Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
# SPDX-License-Identifier: GPL-2.0
menu "Rpmsg drivers"
# RPMSG always gets selected by whoever wants it
config RPMSG
tristate
config RPMSG_CHAR
tristate "RPMSG device interface"
depends on RPMSG
depends on NET
help
Say Y here to export rpmsg endpoints as device files, usually found
in /dev. They make it possible for user-space programs to send and
receive rpmsg packets.
config RPMSG_CTRL
tristate "RPMSG control interface"
depends on RPMSG && ( RPMSG_CHAR || RPMSG_CHAR=n )
help
Say Y here to enable the support of the /dev/rpmsg_ctrlX API. This API
allows user-space programs to create endpoints with specific service name,
source and destination addresses.
config RPMSG_NS
tristate "RPMSG name service announcement"
depends on RPMSG
help
Say Y here to enable the support of the name service announcement
channel that probes the associated RPMsg device on remote endpoint
service announcement.
config RPMSG_MTK_SCP
tristate "MediaTek SCP"
depends on MTK_SCP
select RPMSG
help
Say y here to enable support providing communication channels to
remote processors in MediaTek platforms.
This use IPI and IPC to communicate with remote processors.
config RPMSG_QCOM_GLINK
tristate
select RPMSG
config RPMSG_QCOM_GLINK_RPM
tristate "Qualcomm RPM Glink driver"
select RPMSG_QCOM_GLINK
depends on HAS_IOMEM
depends on MAILBOX
help
Say y here to enable support for the GLINK RPM communication driver,
which serves as a channel for communication with the RPM in GLINK
enabled systems.
config RPMSG_QCOM_GLINK_SMEM
tristate "Qualcomm SMEM Glink driver"
select RPMSG_QCOM_GLINK
depends on MAILBOX
depends on QCOM_SMEM
help
Say y here to enable support for the GLINK SMEM communication driver,
which provides support for using the GLINK communication protocol
over SMEM.
config RPMSG_QCOM_SMD
tristate "Qualcomm Shared Memory Driver (SMD)"
depends on MAILBOX
depends on QCOM_SMEM
select RPMSG
help
Say y here to enable support for the Qualcomm Shared Memory Driver
providing communication channels to remote processors in Qualcomm
platforms.
config RPMSG_VIRTIO
tristate "Virtio RPMSG bus driver"
depends on HAS_DMA
select RPMSG
select RPMSG_NS
select VIRTIO
endmenu
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_RPMSG) += rpmsg_core.o
obj-$(CONFIG_RPMSG_CHAR) += rpmsg_char.o
obj-$(CONFIG_RPMSG_CTRL) += rpmsg_ctrl.o
obj-$(CONFIG_RPMSG_NS) += rpmsg_ns.o
obj-$(CONFIG_RPMSG_MTK_SCP) += mtk_rpmsg.o
CFLAGS_qcom_glink_native.o := -I$(src)
qcom_glink-objs := qcom_glink_native.o qcom_glink_ssr.o
obj-$(CONFIG_RPMSG_QCOM_GLINK) += qcom_glink.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_RPM) += qcom_glink_rpm.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_SMEM) += qcom_glink_smem.o
obj-$(CONFIG_RPMSG_QCOM_SMD) += qcom_smd.o
obj-$(CONFIG_RPMSG_VIRTIO) += virtio_rpmsg_bus.o
+410
View File
@@ -0,0 +1,410 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright 2019 Google LLC.
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/rpmsg/mtk_rpmsg.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include "rpmsg_internal.h"
struct mtk_rpmsg_rproc_subdev {
struct platform_device *pdev;
struct mtk_rpmsg_info *info;
struct rpmsg_endpoint *ns_ept;
struct rproc_subdev subdev;
struct work_struct register_work;
struct list_head channels;
struct mutex channels_lock;
};
#define to_mtk_subdev(d) container_of(d, struct mtk_rpmsg_rproc_subdev, subdev)
struct mtk_rpmsg_channel_info {
struct rpmsg_channel_info info;
bool registered;
struct list_head list;
};
/**
* struct rpmsg_ns_msg - dynamic name service announcement message
* @name: name of remote service that is published
* @addr: address of remote service that is published
*
* This message is sent across to publish a new service. When we receive these
* messages, an appropriate rpmsg channel (i.e device) is created. In turn, the
* ->probe() handler of the appropriate rpmsg driver will be invoked
* (if/as-soon-as one is registered).
*/
struct rpmsg_ns_msg {
char name[RPMSG_NAME_SIZE];
u32 addr;
} __packed;
struct mtk_rpmsg_device {
struct rpmsg_device rpdev;
struct mtk_rpmsg_rproc_subdev *mtk_subdev;
};
struct mtk_rpmsg_endpoint {
struct rpmsg_endpoint ept;
struct mtk_rpmsg_rproc_subdev *mtk_subdev;
};
#define to_mtk_rpmsg_device(r) container_of(r, struct mtk_rpmsg_device, rpdev)
#define to_mtk_rpmsg_endpoint(r) container_of(r, struct mtk_rpmsg_endpoint, ept)
static const struct rpmsg_endpoint_ops mtk_rpmsg_endpoint_ops;
static void __mtk_ept_release(struct kref *kref)
{
struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint,
refcount);
kfree(to_mtk_rpmsg_endpoint(ept));
}
static void mtk_rpmsg_ipi_handler(void *data, unsigned int len, void *priv)
{
struct mtk_rpmsg_endpoint *mept = priv;
struct rpmsg_endpoint *ept = &mept->ept;
int ret;
ret = (*ept->cb)(ept->rpdev, data, len, ept->priv, ept->addr);
if (ret)
dev_warn(&ept->rpdev->dev, "rpmsg handler return error = %d",
ret);
}
static struct rpmsg_endpoint *
__mtk_create_ept(struct mtk_rpmsg_rproc_subdev *mtk_subdev,
struct rpmsg_device *rpdev, rpmsg_rx_cb_t cb, void *priv,
u32 id)
{
struct mtk_rpmsg_endpoint *mept;
struct rpmsg_endpoint *ept;
struct platform_device *pdev = mtk_subdev->pdev;
int ret;
mept = kzalloc_obj(*mept);
if (!mept)
return NULL;
mept->mtk_subdev = mtk_subdev;
ept = &mept->ept;
kref_init(&ept->refcount);
ept->rpdev = rpdev;
ept->cb = cb;
ept->priv = priv;
ept->ops = &mtk_rpmsg_endpoint_ops;
ept->addr = id;
ret = mtk_subdev->info->register_ipi(pdev, id, mtk_rpmsg_ipi_handler,
mept);
if (ret) {
dev_err(&pdev->dev, "IPI register failed, id = %d", id);
kref_put(&ept->refcount, __mtk_ept_release);
return NULL;
}
return ept;
}
static struct rpmsg_endpoint *
mtk_rpmsg_create_ept(struct rpmsg_device *rpdev, rpmsg_rx_cb_t cb, void *priv,
struct rpmsg_channel_info chinfo)
{
struct mtk_rpmsg_rproc_subdev *mtk_subdev =
to_mtk_rpmsg_device(rpdev)->mtk_subdev;
return __mtk_create_ept(mtk_subdev, rpdev, cb, priv, chinfo.src);
}
static void mtk_rpmsg_destroy_ept(struct rpmsg_endpoint *ept)
{
struct mtk_rpmsg_rproc_subdev *mtk_subdev =
to_mtk_rpmsg_endpoint(ept)->mtk_subdev;
mtk_subdev->info->unregister_ipi(mtk_subdev->pdev, ept->addr);
kref_put(&ept->refcount, __mtk_ept_release);
}
static int mtk_rpmsg_send(struct rpmsg_endpoint *ept, const void *data, int len)
{
struct mtk_rpmsg_rproc_subdev *mtk_subdev =
to_mtk_rpmsg_endpoint(ept)->mtk_subdev;
return mtk_subdev->info->send_ipi(mtk_subdev->pdev, ept->addr, data,
len, 0);
}
static int mtk_rpmsg_trysend(struct rpmsg_endpoint *ept, const void *data, int len)
{
struct mtk_rpmsg_rproc_subdev *mtk_subdev =
to_mtk_rpmsg_endpoint(ept)->mtk_subdev;
/*
* TODO: This currently is same as mtk_rpmsg_send, and wait until SCP
* received the last command.
*/
return mtk_subdev->info->send_ipi(mtk_subdev->pdev, ept->addr, data,
len, 0);
}
static const struct rpmsg_endpoint_ops mtk_rpmsg_endpoint_ops = {
.destroy_ept = mtk_rpmsg_destroy_ept,
.send = mtk_rpmsg_send,
.trysend = mtk_rpmsg_trysend,
};
static void mtk_rpmsg_release_device(struct device *dev)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
struct mtk_rpmsg_device *mdev = to_mtk_rpmsg_device(rpdev);
kfree(mdev);
}
static const struct rpmsg_device_ops mtk_rpmsg_device_ops = {
.create_ept = mtk_rpmsg_create_ept,
};
static struct device_node *
mtk_rpmsg_match_device_subnode(struct device_node *node, const char *channel)
{
struct device_node *child;
const char *name;
int ret;
for_each_available_child_of_node(node, child) {
ret = of_property_read_string(child, "mediatek,rpmsg-name", &name);
if (ret)
continue;
if (strcmp(name, channel) == 0)
return child;
}
return NULL;
}
static int mtk_rpmsg_register_device(struct mtk_rpmsg_rproc_subdev *mtk_subdev,
struct rpmsg_channel_info *info)
{
struct rpmsg_device *rpdev;
struct mtk_rpmsg_device *mdev;
struct platform_device *pdev = mtk_subdev->pdev;
mdev = kzalloc_obj(*mdev);
if (!mdev)
return -ENOMEM;
mdev->mtk_subdev = mtk_subdev;
rpdev = &mdev->rpdev;
rpdev->ops = &mtk_rpmsg_device_ops;
rpdev->src = info->src;
rpdev->dst = info->dst;
strscpy(rpdev->id.name, info->name, RPMSG_NAME_SIZE);
rpdev->dev.of_node =
mtk_rpmsg_match_device_subnode(pdev->dev.of_node, info->name);
rpdev->dev.parent = &pdev->dev;
rpdev->dev.release = mtk_rpmsg_release_device;
return rpmsg_register_device(rpdev);
}
static void mtk_register_device_work_function(struct work_struct *register_work)
{
struct mtk_rpmsg_rproc_subdev *subdev = container_of(
register_work, struct mtk_rpmsg_rproc_subdev, register_work);
struct platform_device *pdev = subdev->pdev;
struct mtk_rpmsg_channel_info *info;
int ret;
mutex_lock(&subdev->channels_lock);
list_for_each_entry(info, &subdev->channels, list) {
if (info->registered)
continue;
mutex_unlock(&subdev->channels_lock);
ret = mtk_rpmsg_register_device(subdev, &info->info);
mutex_lock(&subdev->channels_lock);
if (ret) {
dev_err(&pdev->dev, "Can't create rpmsg_device\n");
continue;
}
info->registered = true;
}
mutex_unlock(&subdev->channels_lock);
}
static int mtk_rpmsg_create_device(struct mtk_rpmsg_rproc_subdev *mtk_subdev,
char *name, u32 addr)
{
struct mtk_rpmsg_channel_info *info;
info = kzalloc_obj(*info);
if (!info)
return -ENOMEM;
strscpy(info->info.name, name, RPMSG_NAME_SIZE);
info->info.src = addr;
info->info.dst = RPMSG_ADDR_ANY;
mutex_lock(&mtk_subdev->channels_lock);
list_add(&info->list, &mtk_subdev->channels);
mutex_unlock(&mtk_subdev->channels_lock);
schedule_work(&mtk_subdev->register_work);
return 0;
}
static int mtk_rpmsg_ns_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
struct rpmsg_ns_msg *msg = data;
struct mtk_rpmsg_rproc_subdev *mtk_subdev = priv;
struct device *dev = &mtk_subdev->pdev->dev;
int ret;
if (len != sizeof(*msg)) {
dev_err(dev, "malformed ns msg (%d)\n", len);
return -EINVAL;
}
/*
* the name service ept does _not_ belong to a real rpmsg channel,
* and is handled by the rpmsg bus itself.
* for sanity reasons, make sure a valid rpdev has _not_ sneaked
* in somehow.
*/
if (rpdev) {
dev_err(dev, "anomaly: ns ept has an rpdev handle\n");
return -EINVAL;
}
/* don't trust the remote processor for null terminating the name */
msg->name[RPMSG_NAME_SIZE - 1] = '\0';
dev_info(dev, "creating channel %s addr 0x%x\n", msg->name, msg->addr);
ret = mtk_rpmsg_create_device(mtk_subdev, msg->name, msg->addr);
if (ret) {
dev_err(dev, "create rpmsg device failed\n");
return ret;
}
return 0;
}
static int mtk_rpmsg_prepare(struct rproc_subdev *subdev)
{
struct mtk_rpmsg_rproc_subdev *mtk_subdev = to_mtk_subdev(subdev);
/* a dedicated endpoint handles the name service msgs */
if (mtk_subdev->info->ns_ipi_id >= 0) {
mtk_subdev->ns_ept =
__mtk_create_ept(mtk_subdev, NULL, mtk_rpmsg_ns_cb,
mtk_subdev,
mtk_subdev->info->ns_ipi_id);
if (!mtk_subdev->ns_ept) {
dev_err(&mtk_subdev->pdev->dev,
"failed to create name service endpoint\n");
return -ENOMEM;
}
}
return 0;
}
static void mtk_rpmsg_unprepare(struct rproc_subdev *subdev)
{
struct mtk_rpmsg_rproc_subdev *mtk_subdev = to_mtk_subdev(subdev);
if (mtk_subdev->ns_ept) {
mtk_rpmsg_destroy_ept(mtk_subdev->ns_ept);
mtk_subdev->ns_ept = NULL;
}
}
static void mtk_rpmsg_stop(struct rproc_subdev *subdev, bool crashed)
{
struct mtk_rpmsg_channel_info *info, *next;
struct mtk_rpmsg_rproc_subdev *mtk_subdev = to_mtk_subdev(subdev);
struct device *dev = &mtk_subdev->pdev->dev;
/*
* Destroy the name service endpoint here, to avoid new channel being
* created after the rpmsg_unregister_device loop below.
*/
if (mtk_subdev->ns_ept) {
mtk_rpmsg_destroy_ept(mtk_subdev->ns_ept);
mtk_subdev->ns_ept = NULL;
}
cancel_work_sync(&mtk_subdev->register_work);
mutex_lock(&mtk_subdev->channels_lock);
list_for_each_entry(info, &mtk_subdev->channels, list) {
if (!info->registered)
continue;
if (rpmsg_unregister_device(dev, &info->info)) {
dev_warn(
dev,
"rpmsg_unregister_device failed for %s.%d.%d\n",
info->info.name, info->info.src,
info->info.dst);
}
}
list_for_each_entry_safe(info, next,
&mtk_subdev->channels, list) {
list_del(&info->list);
kfree(info);
}
mutex_unlock(&mtk_subdev->channels_lock);
}
struct rproc_subdev *
mtk_rpmsg_create_rproc_subdev(struct platform_device *pdev,
struct mtk_rpmsg_info *info)
{
struct mtk_rpmsg_rproc_subdev *mtk_subdev;
mtk_subdev = kzalloc_obj(*mtk_subdev);
if (!mtk_subdev)
return NULL;
mtk_subdev->pdev = pdev;
mtk_subdev->subdev.prepare = mtk_rpmsg_prepare;
mtk_subdev->subdev.stop = mtk_rpmsg_stop;
mtk_subdev->subdev.unprepare = mtk_rpmsg_unprepare;
mtk_subdev->info = info;
INIT_LIST_HEAD(&mtk_subdev->channels);
INIT_WORK(&mtk_subdev->register_work,
mtk_register_device_work_function);
mutex_init(&mtk_subdev->channels_lock);
return &mtk_subdev->subdev;
}
EXPORT_SYMBOL_GPL(mtk_rpmsg_create_rproc_subdev);
void mtk_rpmsg_destroy_rproc_subdev(struct rproc_subdev *subdev)
{
struct mtk_rpmsg_rproc_subdev *mtk_subdev = to_mtk_subdev(subdev);
kfree(mtk_subdev);
}
EXPORT_SYMBOL_GPL(mtk_rpmsg_destroy_rproc_subdev);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MediaTek scp rpmsg driver");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2016-2017, Linaro Ltd
*/
#ifndef __QCOM_GLINK_NATIVE_H__
#define __QCOM_GLINK_NATIVE_H__
#include <linux/types.h>
#define GLINK_FEATURE_INTENT_REUSE BIT(0)
#define GLINK_FEATURE_MIGRATION BIT(1)
#define GLINK_FEATURE_TRACER_PKT BIT(2)
struct qcom_glink_pipe {
size_t length;
size_t (*avail)(struct qcom_glink_pipe *glink_pipe);
void (*peek)(struct qcom_glink_pipe *glink_pipe, void *data,
unsigned int offset, size_t count);
void (*advance)(struct qcom_glink_pipe *glink_pipe, size_t count);
void (*write)(struct qcom_glink_pipe *glink_pipe,
const void *hdr, size_t hlen,
const void *data, size_t dlen);
void (*kick)(struct qcom_glink_pipe *glink_pipe);
};
struct device;
struct qcom_glink;
struct qcom_glink *qcom_glink_native_probe(struct device *dev,
unsigned long features,
struct qcom_glink_pipe *rx,
struct qcom_glink_pipe *tx,
bool intentless);
void qcom_glink_native_remove(struct qcom_glink *glink);
void qcom_glink_native_rx(struct qcom_glink *glink);
#endif
+405
View File
@@ -0,0 +1,405 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016-2017, Linaro Ltd
*/
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/mailbox_client.h>
#include "rpmsg_internal.h"
#include "qcom_glink_native.h"
#define RPM_TOC_SIZE 256
#define RPM_TOC_MAGIC 0x67727430 /* grt0 */
#define RPM_TOC_MAX_ENTRIES ((RPM_TOC_SIZE - sizeof(struct rpm_toc)) / \
sizeof(struct rpm_toc_entry))
#define RPM_TX_FIFO_ID 0x61703272 /* ap2r */
#define RPM_RX_FIFO_ID 0x72326170 /* r2ap */
#define to_rpm_pipe(p) container_of(p, struct glink_rpm_pipe, native)
struct rpm_toc_entry {
__le32 id;
__le32 offset;
__le32 size;
} __packed;
struct rpm_toc {
__le32 magic;
__le32 count;
struct rpm_toc_entry entries[];
} __packed;
struct glink_rpm_pipe {
struct qcom_glink_pipe native;
void __iomem *tail;
void __iomem *head;
void __iomem *fifo;
};
struct glink_rpm {
struct qcom_glink *glink;
int irq;
struct mbox_client mbox_client;
struct mbox_chan *mbox_chan;
struct glink_rpm_pipe rx_pipe;
struct glink_rpm_pipe tx_pipe;
};
static size_t glink_rpm_rx_avail(struct qcom_glink_pipe *glink_pipe)
{
struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
unsigned int head;
unsigned int tail;
head = readl(pipe->head);
tail = readl(pipe->tail);
if (head < tail)
return pipe->native.length - tail + head;
else
return head - tail;
}
static void glink_rpm_rx_peek(struct qcom_glink_pipe *glink_pipe,
void *data, unsigned int offset, size_t count)
{
struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
unsigned int tail;
size_t len;
tail = readl(pipe->tail);
tail += offset;
if (tail >= pipe->native.length)
tail -= pipe->native.length;
len = min_t(size_t, count, pipe->native.length - tail);
if (len) {
__ioread32_copy(data, pipe->fifo + tail,
len / sizeof(u32));
}
if (len != count) {
__ioread32_copy(data + len, pipe->fifo,
(count - len) / sizeof(u32));
}
}
static void glink_rpm_rx_advance(struct qcom_glink_pipe *glink_pipe,
size_t count)
{
struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
unsigned int tail;
tail = readl(pipe->tail);
tail += count;
if (tail >= pipe->native.length)
tail -= pipe->native.length;
writel(tail, pipe->tail);
}
static size_t glink_rpm_tx_avail(struct qcom_glink_pipe *glink_pipe)
{
struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
unsigned int head;
unsigned int tail;
head = readl(pipe->head);
tail = readl(pipe->tail);
if (tail <= head)
return pipe->native.length - head + tail;
else
return tail - head;
}
static unsigned int glink_rpm_tx_write_one(struct glink_rpm_pipe *pipe,
unsigned int head,
const void *data, size_t count)
{
size_t len;
len = min_t(size_t, count, pipe->native.length - head);
if (len) {
__iowrite32_copy(pipe->fifo + head, data,
len / sizeof(u32));
}
if (len != count) {
__iowrite32_copy(pipe->fifo, data + len,
(count - len) / sizeof(u32));
}
head += count;
if (head >= pipe->native.length)
head -= pipe->native.length;
return head;
}
static void glink_rpm_tx_write(struct qcom_glink_pipe *glink_pipe,
const void *hdr, size_t hlen,
const void *data, size_t dlen)
{
struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
size_t tlen = hlen + dlen;
size_t aligned_dlen;
unsigned int head;
char padding[8] = {0};
size_t pad;
/* Header length comes from glink native and is always 4 byte aligned */
if (WARN(hlen % 4, "Glink Header length must be 4 bytes aligned\n"))
return;
/*
* Move the unaligned tail of the message to the padding chunk, to
* ensure word aligned accesses
*/
aligned_dlen = ALIGN_DOWN(dlen, 4);
if (aligned_dlen != dlen)
memcpy(padding, data + aligned_dlen, dlen - aligned_dlen);
head = readl(pipe->head);
head = glink_rpm_tx_write_one(pipe, head, hdr, hlen);
head = glink_rpm_tx_write_one(pipe, head, data, aligned_dlen);
pad = ALIGN(tlen, 8) - ALIGN_DOWN(tlen, 4);
if (pad)
head = glink_rpm_tx_write_one(pipe, head, padding, pad);
writel(head, pipe->head);
}
static void glink_rpm_tx_kick(struct qcom_glink_pipe *glink_pipe)
{
struct glink_rpm_pipe *pipe = to_rpm_pipe(glink_pipe);
struct glink_rpm *rpm = container_of(pipe, struct glink_rpm, tx_pipe);
mbox_send_message(rpm->mbox_chan, NULL);
mbox_client_txdone(rpm->mbox_chan, 0);
}
static irqreturn_t qcom_glink_rpm_intr(int irq, void *data)
{
struct glink_rpm *rpm = data;
qcom_glink_native_rx(rpm->glink);
return IRQ_HANDLED;
}
static int glink_rpm_parse_toc(struct device *dev,
void __iomem *msg_ram,
size_t msg_ram_size,
struct glink_rpm_pipe *rx,
struct glink_rpm_pipe *tx)
{
struct rpm_toc *toc;
int num_entries;
unsigned int id;
size_t offset;
size_t size;
void *buf;
int i;
buf = kzalloc(RPM_TOC_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
__ioread32_copy(buf, msg_ram + msg_ram_size - RPM_TOC_SIZE,
RPM_TOC_SIZE / sizeof(u32));
toc = buf;
if (le32_to_cpu(toc->magic) != RPM_TOC_MAGIC) {
dev_err(dev, "RPM TOC has invalid magic\n");
goto err_inval;
}
num_entries = le32_to_cpu(toc->count);
if (num_entries > RPM_TOC_MAX_ENTRIES) {
dev_err(dev, "Invalid number of toc entries\n");
goto err_inval;
}
for (i = 0; i < num_entries; i++) {
id = le32_to_cpu(toc->entries[i].id);
offset = le32_to_cpu(toc->entries[i].offset);
size = le32_to_cpu(toc->entries[i].size);
if (offset > msg_ram_size || offset + size > msg_ram_size) {
dev_err(dev, "TOC entry with invalid size\n");
continue;
}
switch (id) {
case RPM_RX_FIFO_ID:
rx->native.length = size;
rx->tail = msg_ram + offset;
rx->head = msg_ram + offset + sizeof(u32);
rx->fifo = msg_ram + offset + 2 * sizeof(u32);
break;
case RPM_TX_FIFO_ID:
tx->native.length = size;
tx->tail = msg_ram + offset;
tx->head = msg_ram + offset + sizeof(u32);
tx->fifo = msg_ram + offset + 2 * sizeof(u32);
break;
}
}
if (!rx->fifo || !tx->fifo) {
dev_err(dev, "Unable to find rx and tx descriptors\n");
goto err_inval;
}
kfree(buf);
return 0;
err_inval:
kfree(buf);
return -EINVAL;
}
static int glink_rpm_probe(struct platform_device *pdev)
{
struct qcom_glink *glink;
struct glink_rpm *rpm;
struct device_node *np;
void __iomem *msg_ram;
size_t msg_ram_size;
struct device *dev = &pdev->dev;
struct resource r;
int ret;
rpm = devm_kzalloc(&pdev->dev, sizeof(*rpm), GFP_KERNEL);
if (!rpm)
return -ENOMEM;
np = of_parse_phandle(dev->of_node, "qcom,rpm-msg-ram", 0);
ret = of_address_to_resource(np, 0, &r);
of_node_put(np);
if (ret)
return ret;
msg_ram = devm_ioremap(dev, r.start, resource_size(&r));
msg_ram_size = resource_size(&r);
if (!msg_ram)
return -ENOMEM;
ret = glink_rpm_parse_toc(dev, msg_ram, msg_ram_size,
&rpm->rx_pipe, &rpm->tx_pipe);
if (ret)
return ret;
rpm->irq = of_irq_get(dev->of_node, 0);
ret = devm_request_irq(dev, rpm->irq, qcom_glink_rpm_intr,
IRQF_NO_SUSPEND | IRQF_NO_AUTOEN,
"glink-rpm", rpm);
if (ret) {
dev_err(dev, "failed to request IRQ\n");
return ret;
}
rpm->mbox_client.dev = dev;
rpm->mbox_client.knows_txdone = true;
rpm->mbox_chan = mbox_request_channel(&rpm->mbox_client, 0);
if (IS_ERR(rpm->mbox_chan))
return dev_err_probe(dev, PTR_ERR(rpm->mbox_chan), "failed to acquire IPC channel\n");
/* Pipe specific accessors */
rpm->rx_pipe.native.avail = glink_rpm_rx_avail;
rpm->rx_pipe.native.peek = glink_rpm_rx_peek;
rpm->rx_pipe.native.advance = glink_rpm_rx_advance;
rpm->tx_pipe.native.avail = glink_rpm_tx_avail;
rpm->tx_pipe.native.write = glink_rpm_tx_write;
rpm->tx_pipe.native.kick = glink_rpm_tx_kick;
writel(0, rpm->tx_pipe.head);
writel(0, rpm->rx_pipe.tail);
glink = qcom_glink_native_probe(dev,
0,
&rpm->rx_pipe.native,
&rpm->tx_pipe.native,
true);
if (IS_ERR(glink)) {
mbox_free_channel(rpm->mbox_chan);
return PTR_ERR(glink);
}
rpm->glink = glink;
platform_set_drvdata(pdev, rpm);
enable_irq(rpm->irq);
return 0;
}
static void glink_rpm_remove(struct platform_device *pdev)
{
struct glink_rpm *rpm = platform_get_drvdata(pdev);
struct qcom_glink *glink = rpm->glink;
disable_irq(rpm->irq);
qcom_glink_native_remove(glink);
mbox_free_channel(rpm->mbox_chan);
}
static const struct of_device_id glink_rpm_of_match[] = {
{ .compatible = "qcom,glink-rpm" },
{}
};
MODULE_DEVICE_TABLE(of, glink_rpm_of_match);
static struct platform_driver glink_rpm_driver = {
.probe = glink_rpm_probe,
.remove = glink_rpm_remove,
.driver = {
.name = "qcom_glink_rpm",
.of_match_table = glink_rpm_of_match,
},
};
static int __init glink_rpm_init(void)
{
return platform_driver_register(&glink_rpm_driver);
}
subsys_initcall(glink_rpm_init);
static void __exit glink_rpm_exit(void)
{
platform_driver_unregister(&glink_rpm_driver);
}
module_exit(glink_rpm_exit);
MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@linaro.org>");
MODULE_DESCRIPTION("Qualcomm GLINK RPM driver");
MODULE_LICENSE("GPL v2");
+378
View File
@@ -0,0 +1,378 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016, Linaro Ltd
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mailbox_client.h>
#include <linux/mfd/syscon.h>
#include <linux/slab.h>
#include <linux/rpmsg.h>
#include <linux/idr.h>
#include <linux/circ_buf.h>
#include <linux/soc/qcom/smem.h>
#include <linux/sizes.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/workqueue.h>
#include <linux/list.h>
#include <linux/rpmsg/qcom_glink.h>
#include "qcom_glink_native.h"
#define FIFO_FULL_RESERVE 8
#define FIFO_ALIGNMENT 8
#define TX_BLOCKED_CMD_RESERVE 8 /* size of struct read_notif_request */
#define SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR 478
#define SMEM_GLINK_NATIVE_XPRT_FIFO_0 479
#define SMEM_GLINK_NATIVE_XPRT_FIFO_1 480
struct qcom_glink_smem {
struct device dev;
int irq;
struct qcom_glink *glink;
struct mbox_client mbox_client;
struct mbox_chan *mbox_chan;
u32 remote_pid;
};
struct glink_smem_pipe {
struct qcom_glink_pipe native;
__le32 *tail;
__le32 *head;
void *fifo;
struct qcom_glink_smem *smem;
};
#define to_smem_pipe(p) container_of(p, struct glink_smem_pipe, native)
static size_t glink_smem_rx_avail(struct qcom_glink_pipe *np)
{
struct glink_smem_pipe *pipe = to_smem_pipe(np);
struct qcom_glink_smem *smem = pipe->smem;
size_t len;
void *fifo;
u32 head;
u32 tail;
if (!pipe->fifo) {
fifo = qcom_smem_get(smem->remote_pid,
SMEM_GLINK_NATIVE_XPRT_FIFO_1, &len);
if (IS_ERR(fifo)) {
pr_err("failed to acquire RX fifo handle: %ld\n",
PTR_ERR(fifo));
return 0;
}
pipe->fifo = fifo;
pipe->native.length = len;
}
head = le32_to_cpu(*pipe->head);
tail = le32_to_cpu(*pipe->tail);
if (head < tail)
return pipe->native.length - tail + head;
else
return head - tail;
}
static void glink_smem_rx_peek(struct qcom_glink_pipe *np,
void *data, unsigned int offset, size_t count)
{
struct glink_smem_pipe *pipe = to_smem_pipe(np);
size_t len;
u32 tail;
tail = le32_to_cpu(*pipe->tail);
tail += offset;
if (tail >= pipe->native.length)
tail -= pipe->native.length;
len = min_t(size_t, count, pipe->native.length - tail);
if (len)
memcpy_fromio(data, pipe->fifo + tail, len);
if (len != count)
memcpy_fromio(data + len, pipe->fifo, (count - len));
}
static void glink_smem_rx_advance(struct qcom_glink_pipe *np,
size_t count)
{
struct glink_smem_pipe *pipe = to_smem_pipe(np);
u32 tail;
tail = le32_to_cpu(*pipe->tail);
tail += count;
if (tail >= pipe->native.length)
tail -= pipe->native.length;
*pipe->tail = cpu_to_le32(tail);
}
static size_t glink_smem_tx_avail(struct qcom_glink_pipe *np)
{
struct glink_smem_pipe *pipe = to_smem_pipe(np);
u32 head;
u32 tail;
u32 avail;
head = le32_to_cpu(*pipe->head);
tail = le32_to_cpu(*pipe->tail);
if (tail <= head)
avail = pipe->native.length - head + tail;
else
avail = tail - head;
if (avail < (FIFO_FULL_RESERVE + TX_BLOCKED_CMD_RESERVE))
avail = 0;
else
avail -= FIFO_FULL_RESERVE + TX_BLOCKED_CMD_RESERVE;
return avail;
}
static unsigned int glink_smem_tx_write_one(struct glink_smem_pipe *pipe,
unsigned int head,
const void *data, size_t count)
{
size_t len;
len = min_t(size_t, count, pipe->native.length - head);
if (len)
memcpy(pipe->fifo + head, data, len);
if (len != count)
memcpy(pipe->fifo, data + len, count - len);
head += count;
if (head >= pipe->native.length)
head -= pipe->native.length;
return head;
}
static void glink_smem_tx_write(struct qcom_glink_pipe *glink_pipe,
const void *hdr, size_t hlen,
const void *data, size_t dlen)
{
struct glink_smem_pipe *pipe = to_smem_pipe(glink_pipe);
unsigned int head;
head = le32_to_cpu(*pipe->head);
head = glink_smem_tx_write_one(pipe, head, hdr, hlen);
head = glink_smem_tx_write_one(pipe, head, data, dlen);
/* Ensure head is always aligned to 8 bytes */
head = ALIGN(head, 8);
if (head >= pipe->native.length)
head -= pipe->native.length;
/* Ensure ordering of fifo and head update */
wmb();
*pipe->head = cpu_to_le32(head);
}
static void glink_smem_tx_kick(struct qcom_glink_pipe *glink_pipe)
{
struct glink_smem_pipe *pipe = to_smem_pipe(glink_pipe);
struct qcom_glink_smem *smem = pipe->smem;
mbox_send_message(smem->mbox_chan, NULL);
mbox_client_txdone(smem->mbox_chan, 0);
}
static irqreturn_t qcom_glink_smem_intr(int irq, void *data)
{
struct qcom_glink_smem *smem = data;
qcom_glink_native_rx(smem->glink);
return IRQ_HANDLED;
}
static void qcom_glink_smem_release(struct device *dev)
{
struct qcom_glink_smem *smem = container_of(dev, struct qcom_glink_smem, dev);
kfree(smem);
}
struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent,
struct device_node *node)
{
struct glink_smem_pipe *rx_pipe;
struct glink_smem_pipe *tx_pipe;
struct qcom_glink_smem *smem;
struct qcom_glink *glink;
struct device *dev;
u32 remote_pid;
__le32 *descs;
size_t size;
int ret;
smem = kzalloc_obj(*smem);
if (!smem)
return ERR_PTR(-ENOMEM);
dev = &smem->dev;
dev->parent = parent;
dev->of_node = node;
dev->release = qcom_glink_smem_release;
dev_set_name(dev, "%s:%pOFn", dev_name(parent->parent), node);
ret = device_register(dev);
if (ret) {
pr_err("failed to register glink edge\n");
put_device(dev);
return ERR_PTR(ret);
}
ret = of_property_read_u32(dev->of_node, "qcom,remote-pid",
&remote_pid);
if (ret) {
dev_err(dev, "failed to parse qcom,remote-pid\n");
goto err_put_dev;
}
smem->remote_pid = remote_pid;
rx_pipe = devm_kzalloc(dev, sizeof(*rx_pipe), GFP_KERNEL);
tx_pipe = devm_kzalloc(dev, sizeof(*tx_pipe), GFP_KERNEL);
if (!rx_pipe || !tx_pipe) {
ret = -ENOMEM;
goto err_put_dev;
}
ret = qcom_smem_alloc(remote_pid,
SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, 32);
if (ret && ret != -EEXIST) {
dev_err(dev, "failed to allocate glink descriptors\n");
goto err_put_dev;
}
descs = qcom_smem_get(remote_pid,
SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, &size);
if (IS_ERR(descs)) {
dev_err(dev, "failed to acquire xprt descriptor\n");
ret = PTR_ERR(descs);
goto err_put_dev;
}
if (size != 32) {
dev_err(dev, "glink descriptor of invalid size\n");
ret = -EINVAL;
goto err_put_dev;
}
tx_pipe->tail = &descs[0];
tx_pipe->head = &descs[1];
rx_pipe->tail = &descs[2];
rx_pipe->head = &descs[3];
ret = qcom_smem_alloc(remote_pid, SMEM_GLINK_NATIVE_XPRT_FIFO_0,
SZ_16K);
if (ret && ret != -EEXIST) {
dev_err(dev, "failed to allocate TX fifo\n");
goto err_put_dev;
}
tx_pipe->fifo = qcom_smem_get(remote_pid, SMEM_GLINK_NATIVE_XPRT_FIFO_0,
&tx_pipe->native.length);
if (IS_ERR(tx_pipe->fifo)) {
dev_err(dev, "failed to acquire TX fifo\n");
ret = PTR_ERR(tx_pipe->fifo);
goto err_put_dev;
}
smem->irq = of_irq_get(smem->dev.of_node, 0);
ret = devm_request_irq(&smem->dev, smem->irq, qcom_glink_smem_intr,
IRQF_NO_SUSPEND | IRQF_NO_AUTOEN,
"glink-smem", smem);
if (ret) {
dev_err(&smem->dev, "failed to request IRQ\n");
goto err_put_dev;
}
smem->mbox_client.dev = &smem->dev;
smem->mbox_client.knows_txdone = true;
smem->mbox_chan = mbox_request_channel(&smem->mbox_client, 0);
if (IS_ERR(smem->mbox_chan)) {
ret = dev_err_probe(&smem->dev, PTR_ERR(smem->mbox_chan),
"failed to acquire IPC channel\n");
goto err_put_dev;
}
rx_pipe->smem = smem;
rx_pipe->native.avail = glink_smem_rx_avail;
rx_pipe->native.peek = glink_smem_rx_peek;
rx_pipe->native.advance = glink_smem_rx_advance;
tx_pipe->smem = smem;
tx_pipe->native.avail = glink_smem_tx_avail;
tx_pipe->native.write = glink_smem_tx_write;
tx_pipe->native.kick = glink_smem_tx_kick;
*rx_pipe->tail = 0;
*tx_pipe->head = 0;
glink = qcom_glink_native_probe(dev,
GLINK_FEATURE_INTENT_REUSE,
&rx_pipe->native, &tx_pipe->native,
false);
if (IS_ERR(glink)) {
ret = PTR_ERR(glink);
goto err_free_mbox;
}
smem->glink = glink;
enable_irq(smem->irq);
return smem;
err_free_mbox:
mbox_free_channel(smem->mbox_chan);
err_put_dev:
device_unregister(dev);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(qcom_glink_smem_register);
void qcom_glink_smem_unregister(struct qcom_glink_smem *smem)
{
struct qcom_glink *glink = smem->glink;
disable_irq(smem->irq);
qcom_glink_native_remove(glink);
mbox_free_channel(smem->mbox_chan);
device_unregister(&smem->dev);
}
EXPORT_SYMBOL_GPL(qcom_glink_smem_unregister);
MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@linaro.org>");
MODULE_DESCRIPTION("Qualcomm GLINK SMEM driver");
MODULE_LICENSE("GPL v2");
+168
View File
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
* Copyright (c) 2017, Linaro Ltd.
*/
#include <linux/completion.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/rpmsg.h>
#include <linux/rpmsg/qcom_glink.h>
#include <linux/remoteproc/qcom_rproc.h>
/**
* struct do_cleanup_msg - The data structure for an SSR do_cleanup message
* @version: The G-Link SSR protocol version
* @command: The G-Link SSR command - do_cleanup
* @seq_num: Sequence number
* @name_len: Length of the name of the subsystem being restarted
* @name: G-Link edge name of the subsystem being restarted
*/
struct do_cleanup_msg {
__le32 version;
__le32 command;
__le32 seq_num;
__le32 name_len;
char name[32];
};
/**
* struct cleanup_done_msg - The data structure for an SSR cleanup_done message
* @version: The G-Link SSR protocol version
* @response: The G-Link SSR response to a do_cleanup command, cleanup_done
* @seq_num: Sequence number
*/
struct cleanup_done_msg {
__le32 version;
__le32 response;
__le32 seq_num;
};
/*
* G-Link SSR protocol commands
*/
#define GLINK_SSR_DO_CLEANUP 0
#define GLINK_SSR_CLEANUP_DONE 1
struct glink_ssr {
struct device *dev;
struct rpmsg_endpoint *ept;
struct notifier_block nb;
u32 seq_num;
struct completion completion;
};
/* Notifier list for all registered glink_ssr instances */
static BLOCKING_NOTIFIER_HEAD(ssr_notifiers);
/**
* qcom_glink_ssr_notify() - notify GLINK SSR about stopped remoteproc
* @ssr_name: name of the remoteproc that has been stopped
*/
void qcom_glink_ssr_notify(const char *ssr_name)
{
blocking_notifier_call_chain(&ssr_notifiers, 0, (void *)ssr_name);
}
EXPORT_SYMBOL_GPL(qcom_glink_ssr_notify);
static int qcom_glink_ssr_callback(struct rpmsg_device *rpdev,
void *data, int len, void *priv, u32 addr)
{
struct cleanup_done_msg *msg = data;
struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev);
if (len < sizeof(*msg)) {
dev_err(ssr->dev, "message too short\n");
return -EINVAL;
}
if (le32_to_cpu(msg->version) != 0)
return -EINVAL;
if (le32_to_cpu(msg->response) != GLINK_SSR_CLEANUP_DONE)
return 0;
if (le32_to_cpu(msg->seq_num) != ssr->seq_num) {
dev_err(ssr->dev, "invalid sequence number of response\n");
return -EINVAL;
}
complete(&ssr->completion);
return 0;
}
static int qcom_glink_ssr_notifier_call(struct notifier_block *nb,
unsigned long event,
void *data)
{
struct glink_ssr *ssr = container_of(nb, struct glink_ssr, nb);
struct do_cleanup_msg msg;
char *ssr_name = data;
int ret;
ssr->seq_num++;
reinit_completion(&ssr->completion);
memset(&msg, 0, sizeof(msg));
msg.command = cpu_to_le32(GLINK_SSR_DO_CLEANUP);
msg.seq_num = cpu_to_le32(ssr->seq_num);
msg.name_len = cpu_to_le32(strlen(ssr_name));
strscpy(msg.name, ssr_name, sizeof(msg.name));
ret = rpmsg_send(ssr->ept, &msg, sizeof(msg));
if (ret < 0)
dev_err(ssr->dev, "failed to send cleanup message\n");
ret = wait_for_completion_timeout(&ssr->completion, HZ);
if (!ret)
dev_err(ssr->dev, "timeout waiting for cleanup done message\n");
return NOTIFY_DONE;
}
static int qcom_glink_ssr_probe(struct rpmsg_device *rpdev)
{
struct glink_ssr *ssr;
ssr = devm_kzalloc(&rpdev->dev, sizeof(*ssr), GFP_KERNEL);
if (!ssr)
return -ENOMEM;
init_completion(&ssr->completion);
ssr->dev = &rpdev->dev;
ssr->ept = rpdev->ept;
ssr->nb.notifier_call = qcom_glink_ssr_notifier_call;
dev_set_drvdata(&rpdev->dev, ssr);
return blocking_notifier_chain_register(&ssr_notifiers, &ssr->nb);
}
static void qcom_glink_ssr_remove(struct rpmsg_device *rpdev)
{
struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev);
blocking_notifier_chain_unregister(&ssr_notifiers, &ssr->nb);
}
static const struct rpmsg_device_id qcom_glink_ssr_match[] = {
{ "glink_ssr" },
{}
};
MODULE_DEVICE_TABLE(rpmsg, qcom_glink_ssr_match);
static struct rpmsg_driver qcom_glink_ssr_driver = {
.probe = qcom_glink_ssr_probe,
.remove = qcom_glink_ssr_remove,
.callback = qcom_glink_ssr_callback,
.id_table = qcom_glink_ssr_match,
.drv = {
.name = "qcom_glink_ssr",
},
};
module_rpmsg_driver(qcom_glink_ssr_driver);
@@ -0,0 +1,406 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM qcom_glink
#if !defined(__QCOM_GLINK_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ)
#define __QCOM_GLINK_TRACE_H__
#include <linux/tracepoint.h>
#include "qcom_glink_native.h"
TRACE_EVENT(qcom_glink_cmd_version,
TP_PROTO(const char *remote, unsigned int version, unsigned int features, bool tx),
TP_ARGS(remote, version, features, tx),
TP_STRUCT__entry(
__string(remote, remote)
__field(u32, version)
__field(u32, features)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__entry->version = version;
__entry->features = features;
__entry->tx = tx;
),
TP_printk("%s remote: %s version: %u features: %#x",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__entry->version,
__entry->features
)
);
#define trace_qcom_glink_cmd_version_tx(...) trace_qcom_glink_cmd_version(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_version_rx(...) trace_qcom_glink_cmd_version(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_version_ack,
TP_PROTO(const char *remote, unsigned int version, unsigned int features, bool tx),
TP_ARGS(remote, version, features, tx),
TP_STRUCT__entry(
__string(remote, remote)
__field(u32, version)
__field(u32, features)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__entry->version = version;
__entry->features = features;
__entry->tx = tx;
),
TP_printk("%s remote: %s version: %u features: %#x",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__entry->version,
__entry->features
)
);
#define trace_qcom_glink_cmd_version_ack_tx(...) trace_qcom_glink_cmd_version_ack(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_version_ack_rx(...) trace_qcom_glink_cmd_version_ack(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_open,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, bool tx),
TP_ARGS(remote, channel, lcid, rcid, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u]",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid
)
);
#define trace_qcom_glink_cmd_open_tx(...) trace_qcom_glink_cmd_open(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_open_rx(...) trace_qcom_glink_cmd_open(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_close,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, bool tx),
TP_ARGS(remote, channel, lcid, rcid, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u]",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid
)
);
#define trace_qcom_glink_cmd_close_tx(...) trace_qcom_glink_cmd_close(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_close_rx(...) trace_qcom_glink_cmd_close(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_open_ack,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, bool tx),
TP_ARGS(remote, channel, lcid, rcid, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u]",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid
)
);
#define trace_qcom_glink_cmd_open_ack_tx(...) trace_qcom_glink_cmd_open_ack(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_open_ack_rx(...) trace_qcom_glink_cmd_open_ack(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_intent,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, size_t count, size_t size, u32 liid, bool tx),
TP_ARGS(remote, channel, lcid, rcid, count, size, liid, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(u32, count)
__field(u32, size)
__field(u32, liid)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->count = count;
__entry->size = size;
__entry->liid = liid;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u] count: %d [size: %d liid: %d]",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid,
__entry->count,
__entry->size,
__entry->liid
)
);
#define trace_qcom_glink_cmd_intent_tx(...) trace_qcom_glink_cmd_intent(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_intent_rx(...) trace_qcom_glink_cmd_intent(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_rx_done,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, u32 iid, bool reuse, bool tx),
TP_ARGS(remote, channel, lcid, rcid, iid, reuse, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(u32, iid)
__field(bool, reuse)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->iid = iid;
__entry->reuse = reuse;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u] iid: %d reuse: %d",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid,
__entry->iid,
__entry->reuse
)
);
#define trace_qcom_glink_cmd_rx_done_tx(...) trace_qcom_glink_cmd_rx_done(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_rx_done_rx(...) trace_qcom_glink_cmd_rx_done(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_rx_intent_req,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, size_t size, bool tx),
TP_ARGS(remote, channel, lcid, rcid, size, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(u32, size)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->size = size;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u] size: %d",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid,
__entry->size
)
);
#define trace_qcom_glink_cmd_rx_intent_req_tx(...) trace_qcom_glink_cmd_rx_intent_req(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_rx_intent_req_rx(...) trace_qcom_glink_cmd_rx_intent_req(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_rx_intent_req_ack,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, bool granted, bool tx),
TP_ARGS(remote, channel, lcid, rcid, granted, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(bool, granted)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->granted = granted;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u] granted: %d",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid,
__entry->granted
)
);
#define trace_qcom_glink_cmd_rx_intent_req_ack_tx(...) trace_qcom_glink_cmd_rx_intent_req_ack(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_rx_intent_req_ack_rx(...) trace_qcom_glink_cmd_rx_intent_req_ack(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_tx_data,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, u32 iid, u32 chunk_size, u32 left_size, bool cont, bool tx),
TP_ARGS(remote, channel, lcid, rcid, iid, chunk_size, left_size, cont, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(u32, iid)
__field(u32, chunk_size)
__field(u32, left_size)
__field(bool, cont)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->iid = iid;
__entry->chunk_size = chunk_size;
__entry->left_size = left_size;
__entry->cont = cont;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u] iid: %d chunk_size: %d left_size: %d cont: %d",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid,
__entry->iid,
__entry->chunk_size,
__entry->left_size,
__entry->cont
)
);
#define trace_qcom_glink_cmd_tx_data_tx(...) trace_qcom_glink_cmd_tx_data(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_tx_data_rx(...) trace_qcom_glink_cmd_tx_data(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_close_ack,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, bool tx),
TP_ARGS(remote, channel, lcid, rcid, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u]",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid
)
);
#define trace_qcom_glink_cmd_close_ack_tx(...) trace_qcom_glink_cmd_close_ack(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_close_ack_rx(...) trace_qcom_glink_cmd_close_ack(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_read_notif,
TP_PROTO(const char *remote, bool tx),
TP_ARGS(remote, tx),
TP_STRUCT__entry(
__string(remote, remote)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__entry->tx = tx;
),
TP_printk("%s remote: %s",
__entry->tx ? "tx" : "rx",
__get_str(remote)
)
);
#define trace_qcom_glink_cmd_read_notif_tx(...) trace_qcom_glink_cmd_read_notif(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_read_notif_rx(...) trace_qcom_glink_cmd_read_notif(__VA_ARGS__, false)
TRACE_EVENT(qcom_glink_cmd_signal,
TP_PROTO(const char *remote, const char *channel, u16 lcid, u16 rcid, unsigned int signals, bool tx),
TP_ARGS(remote, channel, lcid, rcid, signals, tx),
TP_STRUCT__entry(
__string(remote, remote)
__string(channel, channel)
__field(u16, lcid)
__field(u16, rcid)
__field(u32, signals)
__field(bool, tx)
),
TP_fast_assign(
__assign_str(remote);
__assign_str(channel);
__entry->lcid = lcid;
__entry->rcid = rcid;
__entry->signals = signals;
__entry->tx = tx;
),
TP_printk("%s remote: %s channel: %s[%u/%u] signals: %#x",
__entry->tx ? "tx" : "rx",
__get_str(remote),
__get_str(channel),
__entry->lcid,
__entry->rcid,
__entry->signals
)
);
#define trace_qcom_glink_cmd_signal_tx(...) trace_qcom_glink_cmd_signal(__VA_ARGS__, true)
#define trace_qcom_glink_cmd_signal_rx(...) trace_qcom_glink_cmd_signal(__VA_ARGS__, false)
#endif
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE qcom_glink_trace
#include <trace/define_trace.h>
File diff suppressed because it is too large Load Diff
+582
View File
@@ -0,0 +1,582 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022, STMicroelectronics
* Copyright (c) 2016, Linaro Ltd.
* Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
* Copyright (c) 2012, PetaLogix
* Copyright (c) 2011, Texas Instruments, Inc.
* Copyright (c) 2011, Google, Inc.
*
* Based on rpmsg performance statistics driver by Michal Simek, which in turn
* was based on TI & Google OMX rpmsg driver.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/rpmsg.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <uapi/linux/rpmsg.h>
#include "rpmsg_char.h"
#include "rpmsg_internal.h"
#define RPMSG_DEV_MAX (MINORMASK + 1)
static dev_t rpmsg_major;
static DEFINE_IDA(rpmsg_ept_ida);
static DEFINE_IDA(rpmsg_minor_ida);
#define dev_to_eptdev(dev) container_of(dev, struct rpmsg_eptdev, dev)
#define cdev_to_eptdev(i_cdev) container_of(i_cdev, struct rpmsg_eptdev, cdev)
/**
* struct rpmsg_eptdev - endpoint device context
* @dev: endpoint device
* @cdev: cdev for the endpoint device
* @rpdev: underlaying rpmsg device
* @chinfo: info used to open the endpoint
* @ept_lock: synchronization of @ept modifications
* @ept: rpmsg endpoint reference, when open
* @queue_lock: synchronization of @queue operations
* @queue: incoming message queue
* @readq: wait object for incoming queue
* @default_ept: set to channel default endpoint if the default endpoint should be re-used
* on device open to prevent endpoint address update.
* @remote_flow_restricted: to indicate if the remote has requested for flow to be limited
* @remote_flow_updated: to indicate if the flow control has been requested
*/
struct rpmsg_eptdev {
struct device dev;
struct cdev cdev;
struct rpmsg_device *rpdev;
struct rpmsg_channel_info chinfo;
struct mutex ept_lock;
struct rpmsg_endpoint *ept;
struct rpmsg_endpoint *default_ept;
spinlock_t queue_lock;
struct sk_buff_head queue;
wait_queue_head_t readq;
bool remote_flow_restricted;
bool remote_flow_updated;
};
int rpmsg_chrdev_eptdev_destroy(struct device *dev, void *data)
{
struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
mutex_lock(&eptdev->ept_lock);
eptdev->rpdev = NULL;
if (eptdev->ept) {
/* The default endpoint is released by the rpmsg core */
if (!eptdev->default_ept)
rpmsg_destroy_ept(eptdev->ept);
eptdev->ept = NULL;
}
mutex_unlock(&eptdev->ept_lock);
/* wake up any blocked readers */
wake_up_interruptible(&eptdev->readq);
cdev_device_del(&eptdev->cdev, &eptdev->dev);
put_device(&eptdev->dev);
return 0;
}
EXPORT_SYMBOL(rpmsg_chrdev_eptdev_destroy);
static int rpmsg_ept_cb(struct rpmsg_device *rpdev, void *buf, int len,
void *priv, u32 addr)
{
struct rpmsg_eptdev *eptdev = priv;
struct sk_buff *skb;
if (!eptdev)
return 0;
skb = alloc_skb(len, GFP_ATOMIC);
if (!skb)
return -ENOMEM;
skb_put_data(skb, buf, len);
spin_lock(&eptdev->queue_lock);
skb_queue_tail(&eptdev->queue, skb);
spin_unlock(&eptdev->queue_lock);
/* wake up any blocking processes, waiting for new data */
wake_up_interruptible(&eptdev->readq);
return 0;
}
static int rpmsg_ept_flow_cb(struct rpmsg_device *rpdev, void *priv, bool enable)
{
struct rpmsg_eptdev *eptdev = priv;
if (!eptdev)
return 0;
eptdev->remote_flow_restricted = enable;
eptdev->remote_flow_updated = true;
wake_up_interruptible(&eptdev->readq);
return 0;
}
static int rpmsg_eptdev_open(struct inode *inode, struct file *filp)
{
struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);
struct rpmsg_endpoint *ept;
struct rpmsg_device *rpdev = eptdev->rpdev;
struct device *dev = &eptdev->dev;
mutex_lock(&eptdev->ept_lock);
if (eptdev->ept) {
mutex_unlock(&eptdev->ept_lock);
return -EBUSY;
}
if (!eptdev->rpdev) {
mutex_unlock(&eptdev->ept_lock);
return -ENETRESET;
}
get_device(dev);
/*
* If the default_ept is set, the rpmsg device default endpoint is used.
* Else a new endpoint is created on open that will be destroyed on release.
*/
if (eptdev->default_ept)
ept = eptdev->default_ept;
else
ept = rpmsg_create_ept(rpdev, rpmsg_ept_cb, eptdev, eptdev->chinfo);
if (!ept) {
dev_err(dev, "failed to open %s\n", eptdev->chinfo.name);
put_device(dev);
mutex_unlock(&eptdev->ept_lock);
return -EINVAL;
}
ept->flow_cb = rpmsg_ept_flow_cb;
eptdev->ept = ept;
filp->private_data = eptdev;
mutex_unlock(&eptdev->ept_lock);
return 0;
}
static int rpmsg_eptdev_release(struct inode *inode, struct file *filp)
{
struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);
struct device *dev = &eptdev->dev;
/* Close the endpoint, if it's not already destroyed by the parent */
mutex_lock(&eptdev->ept_lock);
if (eptdev->ept) {
if (!eptdev->default_ept)
rpmsg_destroy_ept(eptdev->ept);
eptdev->ept = NULL;
}
mutex_unlock(&eptdev->ept_lock);
eptdev->remote_flow_updated = false;
/* Discard all SKBs */
skb_queue_purge(&eptdev->queue);
put_device(dev);
return 0;
}
static ssize_t rpmsg_eptdev_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct file *filp = iocb->ki_filp;
struct rpmsg_eptdev *eptdev = filp->private_data;
unsigned long flags;
struct sk_buff *skb;
int use;
if (!eptdev->ept)
return -EPIPE;
spin_lock_irqsave(&eptdev->queue_lock, flags);
/* Wait for data in the queue */
if (skb_queue_empty(&eptdev->queue)) {
spin_unlock_irqrestore(&eptdev->queue_lock, flags);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
/* Wait until we get data or the endpoint goes away */
if (wait_event_interruptible(eptdev->readq,
!skb_queue_empty(&eptdev->queue) ||
!eptdev->ept))
return -ERESTARTSYS;
/* We lost the endpoint while waiting */
if (!eptdev->ept)
return -EPIPE;
spin_lock_irqsave(&eptdev->queue_lock, flags);
}
skb = skb_dequeue(&eptdev->queue);
spin_unlock_irqrestore(&eptdev->queue_lock, flags);
if (!skb)
return -EFAULT;
use = min_t(size_t, iov_iter_count(to), skb->len);
if (copy_to_iter(skb->data, use, to) != use)
use = -EFAULT;
kfree_skb(skb);
return use;
}
static ssize_t rpmsg_eptdev_write_iter(struct kiocb *iocb,
struct iov_iter *from)
{
struct file *filp = iocb->ki_filp;
struct rpmsg_eptdev *eptdev = filp->private_data;
size_t len = iov_iter_count(from);
void *kbuf;
int ret;
kbuf = kzalloc(len, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
if (!copy_from_iter_full(kbuf, len, from)) {
ret = -EFAULT;
goto free_kbuf;
}
if (mutex_lock_interruptible(&eptdev->ept_lock)) {
ret = -ERESTARTSYS;
goto free_kbuf;
}
if (!eptdev->ept) {
ret = -EPIPE;
goto unlock_eptdev;
}
if (filp->f_flags & O_NONBLOCK) {
ret = rpmsg_trysendto(eptdev->ept, kbuf, len, eptdev->chinfo.dst);
if (ret == -ENOMEM)
ret = -EAGAIN;
} else {
ret = rpmsg_sendto(eptdev->ept, kbuf, len, eptdev->chinfo.dst);
}
unlock_eptdev:
mutex_unlock(&eptdev->ept_lock);
free_kbuf:
kfree(kbuf);
return ret < 0 ? ret : len;
}
static __poll_t rpmsg_eptdev_poll(struct file *filp, poll_table *wait)
{
struct rpmsg_eptdev *eptdev = filp->private_data;
__poll_t mask = 0;
if (!eptdev->ept)
return EPOLLERR;
poll_wait(filp, &eptdev->readq, wait);
if (!skb_queue_empty(&eptdev->queue))
mask |= EPOLLIN | EPOLLRDNORM;
if (eptdev->remote_flow_updated)
mask |= EPOLLPRI;
mutex_lock(&eptdev->ept_lock);
mask |= rpmsg_poll(eptdev->ept, filp, wait);
mutex_unlock(&eptdev->ept_lock);
return mask;
}
static long rpmsg_eptdev_ioctl(struct file *fp, unsigned int cmd,
unsigned long arg)
{
struct rpmsg_eptdev *eptdev = fp->private_data;
bool set;
int ret;
switch (cmd) {
case RPMSG_GET_OUTGOING_FLOWCONTROL:
eptdev->remote_flow_updated = false;
ret = put_user(eptdev->remote_flow_restricted, (int __user *)arg);
break;
case RPMSG_SET_INCOMING_FLOWCONTROL:
if (arg > 1) {
ret = -EINVAL;
break;
}
set = !!arg;
ret = rpmsg_set_flow_control(eptdev->ept, set, eptdev->chinfo.dst);
break;
case RPMSG_DESTROY_EPT_IOCTL:
/* Don't allow to destroy a default endpoint. */
if (eptdev->default_ept) {
ret = -EINVAL;
break;
}
ret = rpmsg_chrdev_eptdev_destroy(&eptdev->dev, NULL);
break;
default:
ret = -EINVAL;
}
return ret;
}
static const struct file_operations rpmsg_eptdev_fops = {
.owner = THIS_MODULE,
.open = rpmsg_eptdev_open,
.release = rpmsg_eptdev_release,
.read_iter = rpmsg_eptdev_read_iter,
.write_iter = rpmsg_eptdev_write_iter,
.poll = rpmsg_eptdev_poll,
.unlocked_ioctl = rpmsg_eptdev_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", eptdev->chinfo.name);
}
static DEVICE_ATTR_RO(name);
static ssize_t src_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", eptdev->chinfo.src);
}
static DEVICE_ATTR_RO(src);
static ssize_t dst_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", eptdev->chinfo.dst);
}
static DEVICE_ATTR_RO(dst);
static struct attribute *rpmsg_eptdev_attrs[] = {
&dev_attr_name.attr,
&dev_attr_src.attr,
&dev_attr_dst.attr,
NULL
};
ATTRIBUTE_GROUPS(rpmsg_eptdev);
static void rpmsg_eptdev_release_device(struct device *dev)
{
struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
ida_free(&rpmsg_ept_ida, dev->id);
ida_free(&rpmsg_minor_ida, MINOR(eptdev->dev.devt));
kfree(eptdev);
}
static struct rpmsg_eptdev *rpmsg_chrdev_eptdev_alloc(struct rpmsg_device *rpdev,
struct device *parent)
{
struct rpmsg_eptdev *eptdev;
struct device *dev;
eptdev = kzalloc_obj(*eptdev);
if (!eptdev)
return ERR_PTR(-ENOMEM);
dev = &eptdev->dev;
eptdev->rpdev = rpdev;
mutex_init(&eptdev->ept_lock);
spin_lock_init(&eptdev->queue_lock);
skb_queue_head_init(&eptdev->queue);
init_waitqueue_head(&eptdev->readq);
device_initialize(dev);
dev->class = &rpmsg_class;
dev->parent = parent;
dev->groups = rpmsg_eptdev_groups;
dev_set_drvdata(dev, eptdev);
cdev_init(&eptdev->cdev, &rpmsg_eptdev_fops);
eptdev->cdev.owner = THIS_MODULE;
return eptdev;
}
static int rpmsg_chrdev_eptdev_add(struct rpmsg_eptdev *eptdev, struct rpmsg_channel_info chinfo)
{
struct device *dev = &eptdev->dev;
int ret;
eptdev->chinfo = chinfo;
ret = ida_alloc_max(&rpmsg_minor_ida, RPMSG_DEV_MAX - 1, GFP_KERNEL);
if (ret < 0)
goto free_eptdev;
dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
ret = ida_alloc(&rpmsg_ept_ida, GFP_KERNEL);
if (ret < 0)
goto free_minor_ida;
dev->id = ret;
dev_set_name(dev, "rpmsg%d", ret);
ret = cdev_device_add(&eptdev->cdev, &eptdev->dev);
if (ret)
goto free_ept_ida;
/* We can now rely on the release function for cleanup */
dev->release = rpmsg_eptdev_release_device;
return ret;
free_ept_ida:
ida_free(&rpmsg_ept_ida, dev->id);
free_minor_ida:
ida_free(&rpmsg_minor_ida, MINOR(dev->devt));
free_eptdev:
put_device(dev);
kfree(eptdev);
return ret;
}
int rpmsg_chrdev_eptdev_create(struct rpmsg_device *rpdev, struct device *parent,
struct rpmsg_channel_info chinfo)
{
struct rpmsg_eptdev *eptdev;
eptdev = rpmsg_chrdev_eptdev_alloc(rpdev, parent);
if (IS_ERR(eptdev))
return PTR_ERR(eptdev);
return rpmsg_chrdev_eptdev_add(eptdev, chinfo);
}
EXPORT_SYMBOL(rpmsg_chrdev_eptdev_create);
static int rpmsg_chrdev_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_channel_info chinfo;
struct rpmsg_eptdev *eptdev;
struct device *dev = &rpdev->dev;
int ret;
memcpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
chinfo.src = rpdev->src;
chinfo.dst = rpdev->dst;
eptdev = rpmsg_chrdev_eptdev_alloc(rpdev, dev);
if (IS_ERR(eptdev))
return PTR_ERR(eptdev);
/* Set the default_ept to the rpmsg device endpoint */
eptdev->default_ept = rpdev->ept;
ret = rpmsg_chrdev_eptdev_add(eptdev, chinfo);
if (ret)
return ret;
/*
* The rpmsg_ept_cb uses *priv parameter to get its rpmsg_eptdev context.
* Stored it in default_ept *priv field.
*/
eptdev->default_ept->priv = eptdev;
return 0;
}
static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev)
{
int ret;
ret = device_for_each_child(&rpdev->dev, NULL, rpmsg_chrdev_eptdev_destroy);
if (ret)
dev_warn(&rpdev->dev, "failed to destroy endpoints: %d\n", ret);
}
static struct rpmsg_device_id rpmsg_chrdev_id_table[] = {
{ .name = "rpmsg-raw" },
{ .name = "rpmsg_chrdev" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_chrdev_id_table);
static struct rpmsg_driver rpmsg_chrdev_driver = {
.probe = rpmsg_chrdev_probe,
.remove = rpmsg_chrdev_remove,
.callback = rpmsg_ept_cb,
.id_table = rpmsg_chrdev_id_table,
.drv.name = "rpmsg_chrdev",
};
static int rpmsg_chrdev_init(void)
{
int ret;
ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg_char");
if (ret < 0) {
pr_err("failed to allocate char dev region\n");
return ret;
}
ret = register_rpmsg_driver(&rpmsg_chrdev_driver);
if (ret < 0) {
pr_err("rpmsg: failed to register rpmsg raw driver\n");
goto free_region;
}
return 0;
free_region:
unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
return ret;
}
postcore_initcall(rpmsg_chrdev_init);
static void rpmsg_chrdev_exit(void)
{
unregister_rpmsg_driver(&rpmsg_chrdev_driver);
unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
}
module_exit(rpmsg_chrdev_exit);
MODULE_DESCRIPTION("RPMSG device interface");
MODULE_LICENSE("GPL v2");
+46
View File
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022, STMicroelectronics
*/
#ifndef __RPMSG_CHRDEV_H__
#define __RPMSG_CHRDEV_H__
#if IS_ENABLED(CONFIG_RPMSG_CHAR)
/**
* rpmsg_chrdev_eptdev_create() - register char device based on an endpoint
* @rpdev: prepared rpdev to be used for creating endpoints
* @parent: parent device
* @chinfo: associated endpoint channel information.
*
* This function create a new rpmsg char endpoint device to instantiate a new
* endpoint based on chinfo information.
*/
int rpmsg_chrdev_eptdev_create(struct rpmsg_device *rpdev, struct device *parent,
struct rpmsg_channel_info chinfo);
/**
* rpmsg_chrdev_eptdev_destroy() - destroy created char device endpoint.
* @data: private data associated to the endpoint device
*
* This function destroys a rpmsg char endpoint device created by the RPMSG_DESTROY_EPT_IOCTL
* control.
*/
int rpmsg_chrdev_eptdev_destroy(struct device *dev, void *data);
#else /*IS_ENABLED(CONFIG_RPMSG_CHAR) */
static inline int rpmsg_chrdev_eptdev_create(struct rpmsg_device *rpdev, struct device *parent,
struct rpmsg_channel_info chinfo)
{
return -ENXIO;
}
static inline int rpmsg_chrdev_eptdev_destroy(struct device *dev, void *data)
{
return -ENXIO;
}
#endif /*IS_ENABLED(CONFIG_RPMSG_CHAR) */
#endif /*__RPMSG_CHRDEV_H__ */
+638
View File
@@ -0,0 +1,638 @@
// SPDX-License-Identifier: GPL-2.0
/*
* remote processor messaging bus
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com>
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/of_device.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
#include "rpmsg_internal.h"
const struct class rpmsg_class = {
.name = "rpmsg",
};
EXPORT_SYMBOL(rpmsg_class);
/**
* rpmsg_create_channel() - create a new rpmsg channel
* using its name and address info.
* @rpdev: rpmsg device
* @chinfo: channel_info to bind
*
* Return: a pointer to the new rpmsg device on success, or NULL on error.
*/
struct rpmsg_device *rpmsg_create_channel(struct rpmsg_device *rpdev,
struct rpmsg_channel_info *chinfo)
{
if (WARN_ON(!rpdev))
return NULL;
if (!rpdev->ops || !rpdev->ops->create_channel) {
dev_err(&rpdev->dev, "no create_channel ops found\n");
return NULL;
}
return rpdev->ops->create_channel(rpdev, chinfo);
}
EXPORT_SYMBOL(rpmsg_create_channel);
/**
* rpmsg_release_channel() - release a rpmsg channel
* using its name and address info.
* @rpdev: rpmsg device
* @chinfo: channel_info to bind
*
* Return: 0 on success or an appropriate error value.
*/
int rpmsg_release_channel(struct rpmsg_device *rpdev,
struct rpmsg_channel_info *chinfo)
{
if (WARN_ON(!rpdev))
return -EINVAL;
if (!rpdev->ops || !rpdev->ops->release_channel) {
dev_err(&rpdev->dev, "no release_channel ops found\n");
return -ENXIO;
}
return rpdev->ops->release_channel(rpdev, chinfo);
}
EXPORT_SYMBOL(rpmsg_release_channel);
/**
* rpmsg_create_ept() - create a new rpmsg_endpoint
* @rpdev: rpmsg channel device
* @cb: rx callback handler
* @priv: private data for the driver's use
* @chinfo: channel_info with the local rpmsg address to bind with @cb
*
* Every rpmsg address in the system is bound to an rx callback (so when
* inbound messages arrive, they are dispatched by the rpmsg bus using the
* appropriate callback handler) by means of an rpmsg_endpoint struct.
*
* This function allows drivers to create such an endpoint, and by that,
* bind a callback, and possibly some private data too, to an rpmsg address
* (either one that is known in advance, or one that will be dynamically
* assigned for them).
*
* Simple rpmsg drivers need not call rpmsg_create_ept, because an endpoint
* is already created for them when they are probed by the rpmsg bus
* (using the rx callback provided when they registered to the rpmsg bus).
*
* So things should just work for simple drivers: they already have an
* endpoint, their rx callback is bound to their rpmsg address, and when
* relevant inbound messages arrive (i.e. messages which their dst address
* equals to the src address of their rpmsg channel), the driver's handler
* is invoked to process it.
*
* That said, more complicated drivers might need to allocate
* additional rpmsg addresses, and bind them to different rx callbacks.
* To accomplish that, those drivers need to call this function.
*
* Drivers should provide their @rpdev channel (so the new endpoint would belong
* to the same remote processor their channel belongs to), an rx callback
* function, an optional private data (which is provided back when the
* rx callback is invoked), and an address they want to bind with the
* callback. If @addr is RPMSG_ADDR_ANY, then rpmsg_create_ept will
* dynamically assign them an available rpmsg address (drivers should have
* a very good reason why not to always use RPMSG_ADDR_ANY here).
*
* Return: a pointer to the endpoint on success, or NULL on error.
*/
struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,
rpmsg_rx_cb_t cb, void *priv,
struct rpmsg_channel_info chinfo)
{
if (WARN_ON(!rpdev))
return NULL;
return rpdev->ops->create_ept(rpdev, cb, priv, chinfo);
}
EXPORT_SYMBOL(rpmsg_create_ept);
/**
* rpmsg_destroy_ept() - destroy an existing rpmsg endpoint
* @ept: endpoing to destroy
*
* Should be used by drivers to destroy an rpmsg endpoint previously
* created with rpmsg_create_ept(). As with other types of "free" NULL
* is a valid parameter.
*/
void rpmsg_destroy_ept(struct rpmsg_endpoint *ept)
{
if (ept && ept->ops)
ept->ops->destroy_ept(ept);
}
EXPORT_SYMBOL(rpmsg_destroy_ept);
/**
* rpmsg_send() - send a message across to the remote processor
* @ept: the rpmsg endpoint
* @data: payload of message
* @len: length of payload
*
* This function sends @data of length @len on the @ept endpoint.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to, using @ept's address and its associated rpmsg
* device destination addresses.
* In case there are no TX buffers available, the function will block until
* one becomes available, or a timeout of 15 seconds elapses. When the latter
* happens, -ERESTARTSYS is returned.
*
* Can only be called from process context (for now).
*
* Return: 0 on success and an appropriate error value on failure.
*/
int rpmsg_send(struct rpmsg_endpoint *ept, const void *data, int len)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->send)
return -ENXIO;
return ept->ops->send(ept, data, len);
}
EXPORT_SYMBOL(rpmsg_send);
/**
* rpmsg_sendto() - send a message across to the remote processor, specify dst
* @ept: the rpmsg endpoint
* @data: payload of message
* @len: length of payload
* @dst: destination address
*
* This function sends @data of length @len to the remote @dst address.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to, using @ept's address as source.
* In case there are no TX buffers available, the function will block until
* one becomes available, or a timeout of 15 seconds elapses. When the latter
* happens, -ERESTARTSYS is returned.
*
* Can only be called from process context (for now).
*
* Return: 0 on success and an appropriate error value on failure.
*/
int rpmsg_sendto(struct rpmsg_endpoint *ept, const void *data, int len, u32 dst)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->sendto)
return -ENXIO;
return ept->ops->sendto(ept, data, len, dst);
}
EXPORT_SYMBOL(rpmsg_sendto);
/**
* rpmsg_trysend() - send a message across to the remote processor
* @ept: the rpmsg endpoint
* @data: payload of message
* @len: length of payload
*
* This function sends @data of length @len on the @ept endpoint.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to, using @ept's address as source and its associated
* rpdev's address as destination.
* In case there are no TX buffers available, the function will immediately
* return -ENOMEM without waiting until one becomes available.
*
* Can only be called from process context (for now).
*
* Return: 0 on success and an appropriate error value on failure.
*/
int rpmsg_trysend(struct rpmsg_endpoint *ept, const void *data, int len)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->trysend)
return -ENXIO;
return ept->ops->trysend(ept, data, len);
}
EXPORT_SYMBOL(rpmsg_trysend);
/**
* rpmsg_trysendto() - send a message across to the remote processor, specify dst
* @ept: the rpmsg endpoint
* @data: payload of message
* @len: length of payload
* @dst: destination address
*
* This function sends @data of length @len to the remote @dst address.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to, using @ept's address as source.
* In case there are no TX buffers available, the function will immediately
* return -ENOMEM without waiting until one becomes available.
*
* Can only be called from process context (for now).
*
* Return: 0 on success and an appropriate error value on failure.
*/
int rpmsg_trysendto(struct rpmsg_endpoint *ept, const void *data, int len, u32 dst)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->trysendto)
return -ENXIO;
return ept->ops->trysendto(ept, data, len, dst);
}
EXPORT_SYMBOL(rpmsg_trysendto);
/**
* rpmsg_poll() - poll the endpoint's send buffers
* @ept: the rpmsg endpoint
* @filp: file for poll_wait()
* @wait: poll_table for poll_wait()
*
* Return: mask representing the current state of the endpoint's send buffers
*/
__poll_t rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait)
{
if (WARN_ON(!ept))
return 0;
if (!ept->ops->poll)
return 0;
return ept->ops->poll(ept, filp, wait);
}
EXPORT_SYMBOL(rpmsg_poll);
/**
* rpmsg_set_flow_control() - request remote to pause/resume transmission
* @ept: the rpmsg endpoint
* @pause: pause transmission
* @dst: destination address of the endpoint
*
* Return: 0 on success and an appropriate error value on failure.
*/
int rpmsg_set_flow_control(struct rpmsg_endpoint *ept, bool pause, u32 dst)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->set_flow_control)
return -EOPNOTSUPP;
return ept->ops->set_flow_control(ept, pause, dst);
}
EXPORT_SYMBOL_GPL(rpmsg_set_flow_control);
/**
* rpmsg_get_mtu() - get maximum transmission buffer size for sending message.
* @ept: the rpmsg endpoint
*
* This function returns maximum buffer size available for a single outgoing message.
*
* Return: the maximum transmission size on success and an appropriate error
* value on failure.
*/
ssize_t rpmsg_get_mtu(struct rpmsg_endpoint *ept)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->get_mtu)
return -ENOTSUPP;
return ept->ops->get_mtu(ept);
}
EXPORT_SYMBOL(rpmsg_get_mtu);
/*
* match a rpmsg channel with a channel info struct.
* this is used to make sure we're not creating rpmsg devices for channels
* that already exist.
*/
static int rpmsg_device_match(struct device *dev, const void *data)
{
const struct rpmsg_channel_info *chinfo = data;
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
if (chinfo->src != RPMSG_ADDR_ANY && chinfo->src != rpdev->src)
return 0;
if (chinfo->dst != RPMSG_ADDR_ANY && chinfo->dst != rpdev->dst)
return 0;
if (strncmp(chinfo->name, rpdev->id.name, RPMSG_NAME_SIZE))
return 0;
/* found a match ! */
return 1;
}
struct device *rpmsg_find_device(struct device *parent,
struct rpmsg_channel_info *chinfo)
{
return device_find_child(parent, chinfo, rpmsg_device_match);
}
EXPORT_SYMBOL(rpmsg_find_device);
/* sysfs show configuration fields */
#define rpmsg_show_attr(field, path, format_string) \
static ssize_t \
field##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct rpmsg_device *rpdev = to_rpmsg_device(dev); \
\
return sprintf(buf, format_string, rpdev->path); \
} \
static DEVICE_ATTR_RO(field);
/* for more info, see Documentation/ABI/testing/sysfs-bus-rpmsg */
rpmsg_show_attr(name, id.name, "%s\n");
rpmsg_show_attr(src, src, "0x%x\n");
rpmsg_show_attr(dst, dst, "0x%x\n");
rpmsg_show_attr(announce, announce ? "true" : "false", "%s\n");
static ssize_t modalias_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
ssize_t len;
len = of_device_modalias(dev, buf, PAGE_SIZE);
if (len != -ENODEV)
return len;
return sprintf(buf, RPMSG_DEVICE_MODALIAS_FMT "\n", rpdev->id.name);
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *rpmsg_dev_attrs[] = {
&dev_attr_name.attr,
&dev_attr_modalias.attr,
&dev_attr_dst.attr,
&dev_attr_src.attr,
&dev_attr_announce.attr,
NULL,
};
ATTRIBUTE_GROUPS(rpmsg_dev);
/* rpmsg devices and drivers are matched using the service name */
static inline int rpmsg_id_match(const struct rpmsg_device *rpdev,
const struct rpmsg_device_id *id)
{
return strncmp(id->name, rpdev->id.name, RPMSG_NAME_SIZE) == 0;
}
/* match rpmsg channel and rpmsg driver */
static int rpmsg_dev_match(struct device *dev, const struct device_driver *drv)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
const struct rpmsg_driver *rpdrv = to_rpmsg_driver(drv);
const struct rpmsg_device_id *ids = rpdrv->id_table;
unsigned int i;
int ret;
ret = device_match_driver_override(dev, drv);
if (ret >= 0)
return ret;
if (ids)
for (i = 0; ids[i].name[0]; i++)
if (rpmsg_id_match(rpdev, &ids[i])) {
rpdev->id.driver_data = ids[i].driver_data;
return 1;
}
return of_driver_match_device(dev, drv);
}
static int rpmsg_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct rpmsg_device *rpdev = to_rpmsg_device(dev);
int ret;
ret = of_device_uevent_modalias(dev, env);
if (ret != -ENODEV)
return ret;
return add_uevent_var(env, "MODALIAS=" RPMSG_DEVICE_MODALIAS_FMT,
rpdev->id.name);
}
/*
* when an rpmsg driver is probed with a channel, we seamlessly create
* it an endpoint, binding its rx callback to a unique local rpmsg
* address.
*
* if we need to, we also announce about this channel to the remote
* processor (needed in case the driver is exposing an rpmsg service).
*/
static int rpmsg_dev_probe(struct device *dev)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
struct rpmsg_channel_info chinfo = {};
struct rpmsg_endpoint *ept = NULL;
int err;
err = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON |
PD_FLAG_DETACH_POWER_OFF);
if (err)
goto out;
if (rpdrv->callback) {
strscpy(chinfo.name, rpdev->id.name, sizeof(chinfo.name));
chinfo.src = rpdev->src;
chinfo.dst = RPMSG_ADDR_ANY;
ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);
if (!ept) {
dev_err(dev, "failed to create endpoint\n");
err = -ENOMEM;
goto out;
}
rpdev->ept = ept;
rpdev->src = ept->addr;
ept->flow_cb = rpdrv->flowcontrol;
}
err = rpdrv->probe(rpdev);
if (err) {
dev_err(dev, "%s: failed: %d\n", __func__, err);
goto destroy_ept;
}
if (ept && rpdev->ops->announce_create) {
err = rpdev->ops->announce_create(rpdev);
if (err) {
dev_err(dev, "failed to announce creation\n");
goto remove_rpdev;
}
}
return 0;
remove_rpdev:
if (rpdrv->remove)
rpdrv->remove(rpdev);
destroy_ept:
if (ept)
rpmsg_destroy_ept(ept);
out:
return err;
}
static void rpmsg_dev_remove(struct device *dev)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
if (rpdev->ops->announce_destroy)
rpdev->ops->announce_destroy(rpdev);
if (rpdrv->remove)
rpdrv->remove(rpdev);
if (rpdev->ept)
rpmsg_destroy_ept(rpdev->ept);
}
static const struct bus_type rpmsg_bus = {
.name = "rpmsg",
.match = rpmsg_dev_match,
.dev_groups = rpmsg_dev_groups,
.driver_override = true,
.uevent = rpmsg_uevent,
.probe = rpmsg_dev_probe,
.remove = rpmsg_dev_remove,
};
/*
* A helper for registering rpmsg device with driver override and name.
* Drivers should not be using it, but instead rpmsg_register_device().
*/
int rpmsg_register_device_override(struct rpmsg_device *rpdev,
const char *driver_override)
{
struct device *dev = &rpdev->dev;
int ret;
if (driver_override)
strscpy_pad(rpdev->id.name, driver_override, RPMSG_NAME_SIZE);
dev_set_name(dev, "%s.%s.%d.%d", dev_name(dev->parent),
rpdev->id.name, rpdev->src, rpdev->dst);
dev->bus = &rpmsg_bus;
device_initialize(dev);
if (driver_override) {
ret = device_set_driver_override(dev, driver_override);
if (ret) {
dev_err(dev, "device_set_driver_override() failed: %d\n", ret);
put_device(dev);
return ret;
}
}
ret = device_add(dev);
if (ret) {
dev_err(dev, "device_add failed: %d\n", ret);
put_device(dev);
}
return ret;
}
EXPORT_SYMBOL(rpmsg_register_device_override);
int rpmsg_register_device(struct rpmsg_device *rpdev)
{
return rpmsg_register_device_override(rpdev, NULL);
}
EXPORT_SYMBOL(rpmsg_register_device);
/*
* find an existing channel using its name + address properties,
* and destroy it
*/
int rpmsg_unregister_device(struct device *parent,
struct rpmsg_channel_info *chinfo)
{
struct device *dev;
dev = rpmsg_find_device(parent, chinfo);
if (!dev)
return -EINVAL;
device_unregister(dev);
put_device(dev);
return 0;
}
EXPORT_SYMBOL(rpmsg_unregister_device);
/**
* __register_rpmsg_driver() - register an rpmsg driver with the rpmsg bus
* @rpdrv: pointer to a struct rpmsg_driver
* @owner: owning module/driver
*
* Return: 0 on success, and an appropriate error value on failure.
*/
int __register_rpmsg_driver(struct rpmsg_driver *rpdrv, struct module *owner)
{
rpdrv->drv.bus = &rpmsg_bus;
rpdrv->drv.owner = owner;
return driver_register(&rpdrv->drv);
}
EXPORT_SYMBOL(__register_rpmsg_driver);
/**
* unregister_rpmsg_driver() - unregister an rpmsg driver from the rpmsg bus
* @rpdrv: pointer to a struct rpmsg_driver
*
* Return: 0 on success, and an appropriate error value on failure.
*/
void unregister_rpmsg_driver(struct rpmsg_driver *rpdrv)
{
driver_unregister(&rpdrv->drv);
}
EXPORT_SYMBOL(unregister_rpmsg_driver);
static int __init rpmsg_init(void)
{
int ret;
ret = class_register(&rpmsg_class);
if (ret) {
pr_err("failed to register rpmsg class\n");
return ret;
}
ret = bus_register(&rpmsg_bus);
if (ret) {
pr_err("failed to register rpmsg bus: %d\n", ret);
class_unregister(&rpmsg_class);
}
return ret;
}
postcore_initcall(rpmsg_init);
static void __exit rpmsg_fini(void)
{
bus_unregister(&rpmsg_bus);
class_unregister(&rpmsg_class);
}
module_exit(rpmsg_fini);
MODULE_DESCRIPTION("remote processor messaging bus");
MODULE_LICENSE("GPL v2");
+245
View File
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022, STMicroelectronics
* Copyright (c) 2016, Linaro Ltd.
* Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
* Copyright (c) 2012, PetaLogix
* Copyright (c) 2011, Texas Instruments, Inc.
* Copyright (c) 2011, Google, Inc.
*
* Based on rpmsg performance statistics driver by Michal Simek, which in turn
* was based on TI & Google OMX rpmsg driver.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <uapi/linux/rpmsg.h>
#include "rpmsg_char.h"
#include "rpmsg_internal.h"
#define RPMSG_DEV_MAX (MINORMASK + 1)
static dev_t rpmsg_major;
static DEFINE_IDA(rpmsg_ctrl_ida);
static DEFINE_IDA(rpmsg_minor_ida);
#define dev_to_ctrldev(dev) container_of(dev, struct rpmsg_ctrldev, dev)
#define cdev_to_ctrldev(i_cdev) container_of(i_cdev, struct rpmsg_ctrldev, cdev)
/**
* struct rpmsg_ctrldev - control device for instantiating endpoint devices
* @rpdev: underlaying rpmsg device
* @cdev: cdev for the ctrl device
* @dev: device for the ctrl device
* @ctrl_lock: serialize the ioctrls.
*/
struct rpmsg_ctrldev {
struct rpmsg_device *rpdev;
struct cdev cdev;
struct device dev;
struct mutex ctrl_lock;
};
static int rpmsg_ctrldev_open(struct inode *inode, struct file *filp)
{
struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
get_device(&ctrldev->dev);
filp->private_data = ctrldev;
return 0;
}
static int rpmsg_ctrldev_release(struct inode *inode, struct file *filp)
{
struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
put_device(&ctrldev->dev);
return 0;
}
static long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd,
unsigned long arg)
{
struct rpmsg_ctrldev *ctrldev = fp->private_data;
void __user *argp = (void __user *)arg;
struct rpmsg_endpoint_info eptinfo;
struct rpmsg_channel_info chinfo;
struct rpmsg_device *rpdev;
int ret = 0;
if (copy_from_user(&eptinfo, argp, sizeof(eptinfo)))
return -EFAULT;
memcpy(chinfo.name, eptinfo.name, RPMSG_NAME_SIZE);
chinfo.name[RPMSG_NAME_SIZE - 1] = '\0';
chinfo.src = eptinfo.src;
chinfo.dst = eptinfo.dst;
mutex_lock(&ctrldev->ctrl_lock);
switch (cmd) {
case RPMSG_CREATE_EPT_IOCTL:
ret = rpmsg_chrdev_eptdev_create(ctrldev->rpdev, &ctrldev->dev, chinfo);
break;
case RPMSG_CREATE_DEV_IOCTL:
rpdev = rpmsg_create_channel(ctrldev->rpdev, &chinfo);
if (!rpdev) {
dev_err(&ctrldev->dev, "failed to create %s channel\n", chinfo.name);
ret = -ENXIO;
}
break;
case RPMSG_RELEASE_DEV_IOCTL:
ret = rpmsg_release_channel(ctrldev->rpdev, &chinfo);
if (ret)
dev_err(&ctrldev->dev, "failed to release %s channel (%d)\n",
chinfo.name, ret);
break;
default:
ret = -EINVAL;
}
mutex_unlock(&ctrldev->ctrl_lock);
return ret;
};
static const struct file_operations rpmsg_ctrldev_fops = {
.owner = THIS_MODULE,
.open = rpmsg_ctrldev_open,
.release = rpmsg_ctrldev_release,
.unlocked_ioctl = rpmsg_ctrldev_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
static void rpmsg_ctrldev_release_device(struct device *dev)
{
struct rpmsg_ctrldev *ctrldev = dev_to_ctrldev(dev);
ida_free(&rpmsg_ctrl_ida, dev->id);
ida_free(&rpmsg_minor_ida, MINOR(dev->devt));
kfree(ctrldev);
}
static int rpmsg_ctrldev_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_ctrldev *ctrldev;
struct device *dev;
int ret;
ctrldev = kzalloc_obj(*ctrldev);
if (!ctrldev)
return -ENOMEM;
ctrldev->rpdev = rpdev;
dev = &ctrldev->dev;
device_initialize(dev);
dev->parent = &rpdev->dev;
dev->class = &rpmsg_class;
mutex_init(&ctrldev->ctrl_lock);
cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops);
ctrldev->cdev.owner = THIS_MODULE;
ret = ida_alloc_max(&rpmsg_minor_ida, RPMSG_DEV_MAX - 1, GFP_KERNEL);
if (ret < 0)
goto free_ctrldev;
dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
ret = ida_alloc(&rpmsg_ctrl_ida, GFP_KERNEL);
if (ret < 0)
goto free_minor_ida;
dev->id = ret;
dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", ret);
ret = cdev_device_add(&ctrldev->cdev, &ctrldev->dev);
if (ret)
goto free_ctrl_ida;
/* We can now rely on the release function for cleanup */
dev->release = rpmsg_ctrldev_release_device;
dev_set_drvdata(&rpdev->dev, ctrldev);
return ret;
free_ctrl_ida:
ida_free(&rpmsg_ctrl_ida, dev->id);
free_minor_ida:
ida_free(&rpmsg_minor_ida, MINOR(dev->devt));
free_ctrldev:
put_device(dev);
kfree(ctrldev);
return ret;
}
static void rpmsg_ctrldev_remove(struct rpmsg_device *rpdev)
{
struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);
int ret;
mutex_lock(&ctrldev->ctrl_lock);
/* Destroy all endpoints */
ret = device_for_each_child(&ctrldev->dev, NULL, rpmsg_chrdev_eptdev_destroy);
if (ret)
dev_warn(&rpdev->dev, "failed to nuke endpoints: %d\n", ret);
mutex_unlock(&ctrldev->ctrl_lock);
cdev_device_del(&ctrldev->cdev, &ctrldev->dev);
put_device(&ctrldev->dev);
}
static struct rpmsg_driver rpmsg_ctrldev_driver = {
.probe = rpmsg_ctrldev_probe,
.remove = rpmsg_ctrldev_remove,
.drv = {
.name = "rpmsg_ctrl",
},
};
static int rpmsg_ctrldev_init(void)
{
int ret;
ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg_ctrl");
if (ret < 0) {
pr_err("failed to allocate char dev region\n");
return ret;
}
ret = register_rpmsg_driver(&rpmsg_ctrldev_driver);
if (ret < 0) {
pr_err("failed to register rpmsg driver\n");
unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
}
return ret;
}
postcore_initcall(rpmsg_ctrldev_init);
static void rpmsg_ctrldev_exit(void)
{
unregister_rpmsg_driver(&rpmsg_ctrldev_driver);
unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
}
module_exit(rpmsg_ctrldev_exit);
MODULE_DESCRIPTION("rpmsg control interface");
MODULE_ALIAS("rpmsg:" KBUILD_MODNAME);
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* remote processor messaging bus internals
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com>
*/
#ifndef __RPMSG_INTERNAL_H__
#define __RPMSG_INTERNAL_H__
#include <linux/rpmsg.h>
#include <linux/poll.h>
#define to_rpmsg_device(d) container_of(d, struct rpmsg_device, dev)
#define to_rpmsg_driver(d) container_of_const(d, struct rpmsg_driver, drv)
extern const struct class rpmsg_class;
/**
* struct rpmsg_device_ops - indirection table for the rpmsg_device operations
* @create_channel: create backend-specific channel, optional
* @release_channel: release backend-specific channel, optional
* @create_ept: create backend-specific endpoint, required
* @announce_create: announce presence of new channel, optional
* @announce_destroy: announce destruction of channel, optional
*
* Indirection table for the operations that a rpmsg backend should implement.
* @announce_create and @announce_destroy are optional as the backend might
* advertise new channels implicitly by creating the endpoints.
*/
struct rpmsg_device_ops {
struct rpmsg_device *(*create_channel)(struct rpmsg_device *rpdev,
struct rpmsg_channel_info *chinfo);
int (*release_channel)(struct rpmsg_device *rpdev,
struct rpmsg_channel_info *chinfo);
struct rpmsg_endpoint *(*create_ept)(struct rpmsg_device *rpdev,
rpmsg_rx_cb_t cb, void *priv,
struct rpmsg_channel_info chinfo);
int (*announce_create)(struct rpmsg_device *rpdev);
int (*announce_destroy)(struct rpmsg_device *rpdev);
};
/**
* struct rpmsg_endpoint_ops - indirection table for rpmsg_endpoint operations
* @destroy_ept: see @rpmsg_destroy_ept(), required
* @send: see @rpmsg_send(), required
* @sendto: see @rpmsg_sendto(), optional
* @trysend: see @rpmsg_trysend(), required
* @trysendto: see @rpmsg_trysendto(), optional
* @poll: see @rpmsg_poll(), optional
* @set_flow_control: see @rpmsg_set_flow_control(), optional
* @get_mtu: see @rpmsg_get_mtu(), optional
*
* Indirection table for the operations that a rpmsg backend should implement.
* In addition to @destroy_ept, the backend must at least implement @send and
* @trysend, while the variants sending data off-channel are optional.
*/
struct rpmsg_endpoint_ops {
void (*destroy_ept)(struct rpmsg_endpoint *ept);
int (*send)(struct rpmsg_endpoint *ept, const void *data, int len);
int (*sendto)(struct rpmsg_endpoint *ept, const void *data, int len, u32 dst);
int (*trysend)(struct rpmsg_endpoint *ept, const void *data, int len);
int (*trysendto)(struct rpmsg_endpoint *ept, const void *data, int len, u32 dst);
__poll_t (*poll)(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);
int (*set_flow_control)(struct rpmsg_endpoint *ept, bool pause, u32 dst);
ssize_t (*get_mtu)(struct rpmsg_endpoint *ept);
};
struct device *rpmsg_find_device(struct device *parent,
struct rpmsg_channel_info *chinfo);
struct rpmsg_device *rpmsg_create_channel(struct rpmsg_device *rpdev,
struct rpmsg_channel_info *chinfo);
int rpmsg_release_channel(struct rpmsg_device *rpdev,
struct rpmsg_channel_info *chinfo);
/**
* rpmsg_ctrldev_register_device() - register a char device for control based on rpdev
* @rpdev: prepared rpdev to be used for creating endpoints
*
* This function wraps rpmsg_register_device() preparing the rpdev for use as
* basis for the rpmsg chrdev.
*/
static inline int rpmsg_ctrldev_register_device(struct rpmsg_device *rpdev)
{
return rpmsg_register_device_override(rpdev, "rpmsg_ctrl");
}
#endif
+124
View File
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics 2020 - All Rights Reserved
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/rpmsg/ns.h>
#include <linux/slab.h>
#include "rpmsg_internal.h"
/**
* rpmsg_ns_register_device() - register name service device based on rpdev
* @rpdev: prepared rpdev to be used for creating endpoints
*
* This function wraps rpmsg_register_device() preparing the rpdev for use as
* basis for the rpmsg name service device.
*/
int rpmsg_ns_register_device(struct rpmsg_device *rpdev)
{
rpdev->src = RPMSG_NS_ADDR;
rpdev->dst = RPMSG_NS_ADDR;
return rpmsg_register_device_override(rpdev, "rpmsg_ns");
}
EXPORT_SYMBOL(rpmsg_ns_register_device);
/* invoked when a name service announcement arrives */
static int rpmsg_ns_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
struct rpmsg_ns_msg *msg = data;
struct rpmsg_device *newch;
struct rpmsg_channel_info chinfo;
struct device *dev = rpdev->dev.parent;
int ret;
#if defined(CONFIG_DYNAMIC_DEBUG)
dynamic_hex_dump("NS announcement: ", DUMP_PREFIX_NONE, 16, 1,
data, len, true);
#endif
if (len != sizeof(*msg)) {
dev_err(dev, "malformed ns msg (%d)\n", len);
return -EINVAL;
}
/* don't trust the remote processor for null terminating the name */
msg->name[RPMSG_NAME_SIZE - 1] = '\0';
strscpy_pad(chinfo.name, msg->name, sizeof(chinfo.name));
chinfo.src = RPMSG_ADDR_ANY;
chinfo.dst = rpmsg32_to_cpu(rpdev, msg->addr);
dev_info(dev, "%sing channel %s addr 0x%x\n",
rpmsg32_to_cpu(rpdev, msg->flags) & RPMSG_NS_DESTROY ?
"destroy" : "creat", msg->name, chinfo.dst);
if (rpmsg32_to_cpu(rpdev, msg->flags) & RPMSG_NS_DESTROY) {
ret = rpmsg_release_channel(rpdev, &chinfo);
if (ret)
dev_err(dev, "rpmsg_destroy_channel failed: %d\n", ret);
} else {
newch = rpmsg_create_channel(rpdev, &chinfo);
if (!newch)
dev_err(dev, "rpmsg_create_channel failed\n");
}
return 0;
}
static int rpmsg_ns_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_endpoint *ns_ept;
struct rpmsg_channel_info ns_chinfo = {
.src = RPMSG_NS_ADDR,
.dst = RPMSG_NS_ADDR,
.name = "name_service",
};
/*
* Create the NS announcement service endpoint associated to the RPMsg
* device. The endpoint will be automatically destroyed when the RPMsg
* device will be deleted.
*/
ns_ept = rpmsg_create_ept(rpdev, rpmsg_ns_cb, NULL, ns_chinfo);
if (!ns_ept) {
dev_err(&rpdev->dev, "failed to create the ns ept\n");
return -ENOMEM;
}
rpdev->ept = ns_ept;
return 0;
}
static struct rpmsg_driver rpmsg_ns_driver = {
.drv.name = KBUILD_MODNAME,
.probe = rpmsg_ns_probe,
};
static int rpmsg_ns_init(void)
{
int ret;
ret = register_rpmsg_driver(&rpmsg_ns_driver);
if (ret < 0)
pr_err("%s: Failed to register rpmsg driver\n", __func__);
return ret;
}
postcore_initcall(rpmsg_ns_init);
static void rpmsg_ns_exit(void)
{
unregister_rpmsg_driver(&rpmsg_ns_driver);
}
module_exit(rpmsg_ns_exit);
MODULE_DESCRIPTION("Name service announcement rpmsg driver");
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
MODULE_ALIAS("rpmsg:" KBUILD_MODNAME);
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff