Merge "soc: qcom: Add snapshot of GLINK_PKT Driver"

This commit is contained in:
qctecmdr 2022-10-25 18:31:45 -07:00 committed by Gerrit - the friendly Code Review server
commit ea04aa224b
4 changed files with 1196 additions and 0 deletions

View File

@ -309,6 +309,24 @@ config QSEE_IPC_IRQ_BRIDGE
userspace. The interrupt will be propagated through a character device
that userspace clients can poll on.
config QCOM_GLINK
tristate "GLINK Probe Helper"
depends on RPMSG_QCOM_GLINK_SMEM
help
This enables the GLINK Probe module. This is designed to set up
other edges in the system. It will initialize all the transports for
all the edges present in the device.
Say M if you want to enable this module.
config QCOM_GLINK_PKT
tristate "Enable device interface for GLINK packet channels"
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SLATECOM
help
G-link packet driver provides the interface for the userspace
clients to communicate over G-Link via device nodes.
This enable the userspace clients to read and write to
some glink packets channel.
config QTI_PMIC_GLINK
tristate "Enable support for PMIC GLINK"
depends on RPMSG

View File

@ -23,6 +23,8 @@ obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
obj-$(CONFIG_QCOM_SMP2P_SLEEPSTATE) += smp2p_sleepstate.o
obj-$(CONFIG_QSEE_IPC_IRQ_BRIDGE) += qsee_ipc_irq_bridge.o
obj-$(CONFIG_QCOM_GLINK) += glink_probe.o
obj-$(CONFIG_QCOM_GLINK_PKT) += glink_pkt.o
obj-$(CONFIG_QTI_PMIC_GLINK) += pmic_glink.o
obj-$(CONFIG_QTI_BATTERY_GLINK_DEBUG) += qti_battery_debug.o
obj-$(CONFIG_QTI_CHARGER_ULOG_GLINK) += charger-ulog-glink.o

View File

@ -0,0 +1,938 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */
#include <linux/platform_device.h>
#include <linux/ipc_logging.h>
#include <linux/refcount.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/rpmsg.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/idr.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/termios.h>
/* Define IPC Logging Macros */
#define GLINK_PKT_IPC_LOG_PAGE_CNT 2
static void *glink_pkt_ilctxt;
#define GLINK_PKT_INFO(x, ...) \
ipc_log_string(glink_pkt_ilctxt, "[%s]: "x, __func__, ##__VA_ARGS__)
#define GLINK_PKT_ERR(x, ...) \
do { \
printk_ratelimited("%s[%s]: " x, KERN_ERR, __func__, ##__VA_ARGS__); \
ipc_log_string(glink_pkt_ilctxt, "%s[%s]: " x, "", __func__, \
##__VA_ARGS__);\
} while (0)
#define GLINK_PKT_IOCTL_MAGIC (0xC3)
#define GLINK_PKT_IOCTL_QUEUE_RX_INTENT \
_IOW(GLINK_PKT_IOCTL_MAGIC, 0, unsigned int)
#define MODULE_NAME "glink_pkt"
static dev_t glink_pkt_major;
static struct class *glink_pkt_class;
static int num_glink_pkt_devs;
static DEFINE_IDA(glink_pkt_minor_ida);
/**
* struct glink_pkt - driver context, relates rpdev to cdev
* @dev: glink pkt device
* @cdev: cdev for the glink pkt device
* @drv: rpmsg driver for registering to rpmsg bus
* @lock: synchronization of @rpdev and @open_tout modifications
* @ch_open: wait object for opening the glink channel
* @refcount: count how many userspace clients have handles
* @rpdev: underlaying rpmsg device
* @queue_lock: synchronization of @queue operations
* @queue: incoming message queue
* @readq: wait object for incoming queue
* @sig_change: flag to indicate serial signal change
* @fragmented_read: set from dt node for partial read
* @dev_name: /dev/@dev_name for glink_pkt device
* @ch_name: glink channel to match to
* @edge: glink edge to match to
* @open_tout: timeout for open syscall, configurable in sysfs
* @rskb: current skb being read
* @rdata: data pointer in current skb
* @rdata_len: remaining data to be read from skb
*/
struct glink_pkt_device {
struct device dev;
struct cdev cdev;
struct rpmsg_driver drv;
struct mutex lock;
struct completion ch_open;
refcount_t refcount;
struct rpmsg_device *rpdev;
spinlock_t queue_lock;
struct sk_buff_head queue;
wait_queue_head_t readq;
int sig_change;
bool fragmented_read;
bool enable_ch_close;
int drv_registered;
const char *dev_name;
const char *ch_name;
const char *edge;
int open_tout;
struct sk_buff *rskb;
unsigned char *rdata;
size_t rdata_len;
};
#define dev_to_gpdev(_dev) container_of(_dev, struct glink_pkt_device, dev)
#define cdev_to_gpdev(_cdev) container_of(_cdev, struct glink_pkt_device, cdev)
#define drv_to_rpdrv(_drv) container_of(_drv, struct rpmsg_driver, drv)
#define rpdrv_to_gpdev(_rdrv) container_of(_rdrv, struct glink_pkt_device, drv)
static ssize_t open_timeout_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t n)
{
struct glink_pkt_device *gpdev = dev_to_gpdev(dev);
long tmp;
mutex_lock(&gpdev->lock);
if (kstrtol(buf, 0, &tmp)) {
mutex_unlock(&gpdev->lock);
GLINK_PKT_ERR("unable to convert string to int for /dev/%s\n",
gpdev->dev_name);
return -EINVAL;
}
gpdev->open_tout = tmp;
mutex_unlock(&gpdev->lock);
return n;
}
static ssize_t open_timeout_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct glink_pkt_device *gpdev = dev_to_gpdev(dev);
ssize_t ret;
mutex_lock(&gpdev->lock);
ret = scnprintf(buf, PAGE_SIZE, "%d\n", gpdev->open_tout);
mutex_unlock(&gpdev->lock);
return ret;
}
static DEVICE_ATTR_RW(open_timeout);
static int glink_pkt_rpdev_probe(struct rpmsg_device *rpdev)
{
struct device_driver *drv = rpdev->dev.driver;
struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv);
struct glink_pkt_device *gpdev = rpdrv_to_gpdev(rpdrv);
mutex_lock(&gpdev->lock);
gpdev->rpdev = rpdev;
mutex_unlock(&gpdev->lock);
dev_set_drvdata(&rpdev->dev, gpdev);
complete_all(&gpdev->ch_open);
return 0;
}
static int glink_pkt_rpdev_cb(struct rpmsg_device *rpdev, void *buf, int len,
void *priv, u32 addr)
{
struct glink_pkt_device *gpdev = dev_get_drvdata(&rpdev->dev);
unsigned long flags;
struct sk_buff *skb;
if (!gpdev) {
GLINK_PKT_ERR("channel is in reset\n");
return -ENETRESET;
}
skb = alloc_skb(len, GFP_ATOMIC);
if (!skb)
return -ENOMEM;
skb_put_data(skb, buf, len);
spin_lock_irqsave(&gpdev->queue_lock, flags);
skb_queue_tail(&gpdev->queue, skb);
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
/* wake up any blocking processes, waiting for new data */
wake_up_interruptible(&gpdev->readq);
return 0;
}
static int glink_pkt_rpdev_sigs(struct rpmsg_device *rpdev, void *priv,
u32 old, u32 new)
{
struct device_driver *drv = rpdev->dev.driver;
struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv);
struct glink_pkt_device *gpdev = rpdrv_to_gpdev(rpdrv);
unsigned long flags;
spin_lock_irqsave(&gpdev->queue_lock, flags);
gpdev->sig_change = true;
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
/* wake up any blocking processes, waiting for new data */
wake_up_interruptible(&gpdev->readq);
return 0;
}
static void glink_pkt_rpdev_remove(struct rpmsg_device *rpdev)
{
struct device_driver *drv = rpdev->dev.driver;
struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv);
struct glink_pkt_device *gpdev = rpdrv_to_gpdev(rpdrv);
mutex_lock(&gpdev->lock);
gpdev->rpdev = NULL;
mutex_unlock(&gpdev->lock);
dev_set_drvdata(&rpdev->dev, NULL);
/* wake up any blocked readers */
reinit_completion(&gpdev->ch_open);
wake_up_interruptible(&gpdev->readq);
}
/**
* glink_pkt_open() - open() syscall for the glink_pkt device
* inode: Pointer to the inode structure.
* file: Pointer to the file structure.
*
* This function is used to open the glink pkt device when
* userspace client do a open() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static int glink_pkt_open(struct inode *inode, struct file *file)
{
struct glink_pkt_device *gpdev = cdev_to_gpdev(inode->i_cdev);
int tout = msecs_to_jiffies(gpdev->open_tout * 1000);
struct device *dev = &gpdev->dev;
int ret;
refcount_inc(&gpdev->refcount);
get_device(dev);
GLINK_PKT_INFO("begin for %s by %s:%d ref_cnt[%d]\n",
gpdev->ch_name, current->comm,
task_pid_nr(current), refcount_read(&gpdev->refcount));
if (!gpdev->drv_registered && gpdev->enable_ch_close) {
register_rpmsg_driver(&gpdev->drv);
gpdev->drv_registered = 1;
}
ret = wait_for_completion_interruptible_timeout(&gpdev->ch_open, tout);
if (ret <= 0) {
if (gpdev->drv_registered && gpdev->enable_ch_close) {
unregister_rpmsg_driver(&gpdev->drv);
gpdev->drv_registered = 0;
}
refcount_dec(&gpdev->refcount);
put_device(dev);
GLINK_PKT_INFO("timeout for %s by %s:%d\n", gpdev->ch_name,
current->comm, task_pid_nr(current));
return -ETIMEDOUT;
}
file->private_data = gpdev;
GLINK_PKT_INFO("end for %s by %s:%d ref_cnt[%d]\n",
gpdev->ch_name, current->comm,
task_pid_nr(current), refcount_read(&gpdev->refcount));
return 0;
}
/**
* glink_pkt_release() - release operation on glink_pkt device
* inode: Pointer to the inode structure.
* file: Pointer to the file structure.
*
* This function is used to release the glink pkt device when
* userspace client do a close() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static int glink_pkt_release(struct inode *inode, struct file *file)
{
struct glink_pkt_device *gpdev = cdev_to_gpdev(inode->i_cdev);
struct device *dev = &gpdev->dev;
struct sk_buff *skb;
unsigned long flags;
GLINK_PKT_INFO("for %s by %s:%d ref_cnt[%d]\n",
gpdev->ch_name, current->comm,
task_pid_nr(current), refcount_read(&gpdev->refcount));
refcount_dec(&gpdev->refcount);
if (refcount_read(&gpdev->refcount) == 1) {
spin_lock_irqsave(&gpdev->queue_lock, flags);
if (gpdev->rskb) {
kfree_skb(gpdev->rskb);
gpdev->rskb = NULL;
gpdev->rdata = NULL;
gpdev->rdata_len = 0;
}
/* Discard all SKBs */
while (!skb_queue_empty(&gpdev->queue)) {
skb = skb_dequeue(&gpdev->queue);
kfree_skb(skb);
}
wake_up_interruptible(&gpdev->readq);
gpdev->sig_change = false;
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
if (gpdev->drv_registered && gpdev->enable_ch_close) {
unregister_rpmsg_driver(&gpdev->drv);
gpdev->drv_registered = 0;
}
}
if (gpdev->drv_registered && gpdev->enable_ch_close) {
unregister_rpmsg_driver(&gpdev->drv);
gpdev->drv_registered = 0;
}
put_device(dev);
return 0;
}
/**
* glink_pkt_read() - read() syscall for the glink_pkt device
* file: Pointer to the file structure.
* buf: Pointer to the userspace buffer.
* count: Number bytes to read from the file.
* ppos: Pointer to the position into the file.
*
* This function is used to Read the data from glink pkt device when
* userspace client do a read() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static ssize_t glink_pkt_read(struct file *file,
char __user *buf, size_t count, loff_t *ppos)
{
struct glink_pkt_device *gpdev = file->private_data;
unsigned long flags;
int use;
if (!gpdev || refcount_read(&gpdev->refcount) == 1) {
GLINK_PKT_ERR("invalid device handle\n");
return -EINVAL;
}
if (!completion_done(&gpdev->ch_open)) {
GLINK_PKT_ERR("%s channel in reset\n", gpdev->ch_name);
return -ENETRESET;
}
GLINK_PKT_INFO("begin for %s by %s:%d ref_cnt[%d], remaining[%d], count[%d]\n",
gpdev->ch_name, current->comm,
task_pid_nr(current), refcount_read(&gpdev->refcount),
gpdev->rdata_len, count);
spin_lock_irqsave(&gpdev->queue_lock, flags);
/* Wait for data in the queue */
if (skb_queue_empty(&gpdev->queue) && !gpdev->rskb) {
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
/* Wait until we get data or the endpoint goes away */
if (wait_event_interruptible(gpdev->readq,
!skb_queue_empty(&gpdev->queue) ||
!completion_done(&gpdev->ch_open)))
return -ERESTARTSYS;
/* We lost the endpoint while waiting */
if (!completion_done(&gpdev->ch_open))
return -ENETRESET;
spin_lock_irqsave(&gpdev->queue_lock, flags);
}
if (!gpdev->rskb) {
gpdev->rskb = skb_dequeue(&gpdev->queue);
if (!gpdev->rskb) {
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
return -EFAULT;
}
gpdev->rdata = gpdev->rskb->data;
gpdev->rdata_len = gpdev->rskb->len;
}
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
use = min_t(size_t, count, gpdev->rdata_len);
if (copy_to_user(buf, gpdev->rdata, use))
use = -EFAULT;
if (!gpdev->fragmented_read && gpdev->rdata_len == use) {
struct sk_buff *skb = gpdev->rskb;
spin_lock_irqsave(&gpdev->queue_lock, flags);
gpdev->rskb = NULL;
gpdev->rdata = NULL;
gpdev->rdata_len = 0;
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
kfree_skb(skb);
} else {
struct sk_buff *skb = NULL;
spin_lock_irqsave(&gpdev->queue_lock, flags);
gpdev->rdata += use;
gpdev->rdata_len -= use;
if (gpdev->rdata_len == 0) {
skb = gpdev->rskb;
gpdev->rskb = NULL;
gpdev->rdata = NULL;
gpdev->rdata_len = 0;
}
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
if (skb)
kfree_skb(skb);
}
GLINK_PKT_INFO("end for %s by %s:%d ret[%d], remaining[%d]\n", gpdev->ch_name,
current->comm, task_pid_nr(current), use, gpdev->rdata_len);
return use;
}
/**
* glink_pkt_write() - write() syscall for the glink_pkt device
* file: Pointer to the file structure.
* buf: Pointer to the userspace buffer.
* count: Number bytes to read from the file.
* ppos: Pointer to the position into the file.
*
* This function is used to write the data to glink pkt device when
* userspace client do a write() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static ssize_t glink_pkt_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
struct glink_pkt_device *gpdev = file->private_data;
void *kbuf;
int ret;
gpdev = file->private_data;
if (!gpdev || refcount_read(&gpdev->refcount) == 1) {
GLINK_PKT_ERR("invalid device handle\n");
return -EINVAL;
}
GLINK_PKT_INFO("begin to %s buffer_size %zu\n", gpdev->ch_name, count);
kbuf = memdup_user(buf, count);
if (IS_ERR(kbuf))
return PTR_ERR(kbuf);
if (mutex_lock_interruptible(&gpdev->lock)) {
ret = -ERESTARTSYS;
goto free_kbuf;
}
if (!completion_done(&gpdev->ch_open) || !gpdev->rpdev) {
GLINK_PKT_ERR("%s channel in reset\n", gpdev->ch_name);
ret = -ENETRESET;
goto unlock_ch;
}
if (file->f_flags & O_NONBLOCK)
ret = rpmsg_trysend(gpdev->rpdev->ept, kbuf, count);
else
ret = rpmsg_send(gpdev->rpdev->ept, kbuf, count);
unlock_ch:
mutex_unlock(&gpdev->lock);
free_kbuf:
kfree(kbuf);
GLINK_PKT_INFO("finish to %s ret %d\n", gpdev->ch_name, ret);
return ret < 0 ? ret : count;
}
/**
* glink_pkt_poll() - poll() syscall for the glink_pkt device
* file: Pointer to the file structure.
* wait: pointer to Poll table.
*
* This function is used to poll on the glink pkt device when
* userspace client do a poll() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static __poll_t glink_pkt_poll(struct file *file, poll_table *wait)
{
struct glink_pkt_device *gpdev = file->private_data;
__poll_t mask = 0;
unsigned long flags;
gpdev = file->private_data;
if (!gpdev || refcount_read(&gpdev->refcount) == 1) {
GLINK_PKT_ERR("invalid device handle\n");
return POLLERR;
}
if (!completion_done(&gpdev->ch_open)) {
GLINK_PKT_ERR("%s channel in reset\n", gpdev->ch_name);
return POLLHUP | POLLPRI;
}
poll_wait(file, &gpdev->readq, wait);
mutex_lock(&gpdev->lock);
if (!completion_done(&gpdev->ch_open) || !gpdev->rpdev) {
GLINK_PKT_ERR("%s channel reset after wait\n", gpdev->ch_name);
mutex_unlock(&gpdev->lock);
return POLLHUP;
}
spin_lock_irqsave(&gpdev->queue_lock, flags);
if (!skb_queue_empty(&gpdev->queue) || gpdev->rskb)
mask |= POLLIN | POLLRDNORM;
if (gpdev->sig_change)
mask |= POLLPRI;
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
mask |= rpmsg_poll(gpdev->rpdev->ept, file, wait);
mutex_unlock(&gpdev->lock);
return mask;
}
/**
* glink_pkt_tiocmset() - set the signals for glink_pkt device
* devp: Pointer to the glink_pkt device structure.
* cmd: IOCTL command.
* arg: Arguments to the ioctl call.
*
* This function is used to set the signals on the glink pkt device
* when userspace client do a ioctl() system call with TIOCMBIS,
* TIOCMBIC and TICOMSET.
*/
static int glink_pkt_tiocmset(struct glink_pkt_device *gpdev, unsigned int cmd,
int __user *arg)
{
u32 set, clear, val;
int ret;
ret = get_user(val, arg);
if (ret)
return ret;
set = clear = 0;
switch (cmd) {
case TIOCMBIS:
set = val;
break;
case TIOCMBIC:
clear = val;
break;
case TIOCMSET:
set = val;
clear = ~val;
break;
}
set &= TIOCM_DTR | TIOCM_RTS | TIOCM_CD | TIOCM_RI;
clear &= TIOCM_DTR | TIOCM_RTS | TIOCM_CD | TIOCM_RI;
GLINK_PKT_INFO("set[0x%x] clear[0x%x]\n", set, clear);
return rpmsg_set_signals(gpdev->rpdev->ept, set, clear);
}
/**
* glink_pkt_ioctl() - ioctl() syscall for the glink_pkt device
* file: Pointer to the file structure.
* cmd: IOCTL command.
* arg: Arguments to the ioctl call.
*
* This function is used to ioctl on the glink pkt device when
* userspace client do a ioctl() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static long glink_pkt_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct glink_pkt_device *gpdev;
unsigned long flags;
int ret;
gpdev = file->private_data;
if (!gpdev || refcount_read(&gpdev->refcount) == 1) {
GLINK_PKT_ERR("invalid device handle\n");
return -EINVAL;
}
if (mutex_lock_interruptible(&gpdev->lock))
return -ERESTARTSYS;
if (!completion_done(&gpdev->ch_open)) {
GLINK_PKT_ERR("%s channel in reset\n", gpdev->ch_name);
mutex_unlock(&gpdev->lock);
return -ENETRESET;
}
switch (cmd) {
case TIOCMGET:
spin_lock_irqsave(&gpdev->queue_lock, flags);
gpdev->sig_change = false;
spin_unlock_irqrestore(&gpdev->queue_lock, flags);
ret = rpmsg_get_signals(gpdev->rpdev->ept);
if (ret >= 0)
ret = put_user(ret, (int __user *)arg);
break;
case TIOCMSET:
case TIOCMBIS:
case TIOCMBIC:
ret = glink_pkt_tiocmset(gpdev, cmd, (int __user *)arg);
break;
case GLINK_PKT_IOCTL_QUEUE_RX_INTENT:
/* Return success to not break userspace client logic */
ret = 0;
break;
default:
GLINK_PKT_ERR("unrecognized ioctl command 0x%x\n", cmd);
ret = -ENOIOCTLCMD;
}
mutex_unlock(&gpdev->lock);
return ret;
}
static const struct file_operations glink_pkt_fops = {
.owner = THIS_MODULE,
.open = glink_pkt_open,
.release = glink_pkt_release,
.read = glink_pkt_read,
.write = glink_pkt_write,
.poll = glink_pkt_poll,
.unlocked_ioctl = glink_pkt_ioctl,
.compat_ioctl = glink_pkt_ioctl,
};
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct glink_pkt_device *gpdev = dev_to_gpdev(dev);
return scnprintf(buf, RPMSG_NAME_SIZE, "%s\n", gpdev->ch_name);
}
static DEVICE_ATTR_RO(name);
static struct attribute *glink_pkt_device_attrs[] = {
&dev_attr_name.attr,
NULL,
};
ATTRIBUTE_GROUPS(glink_pkt_device);
/**
* parse_glinkpkt_devicetree() - parse device tree binding for a subnode
*
* np: pointer to a device tree node
* gpdev: pointer to GLINK PACKET device
*
* Return: 0 on success, standard Linux error codes on error.
*/
static int glink_pkt_parse_devicetree(struct device_node *np,
struct glink_pkt_device *gpdev)
{
char *key;
int ret;
key = "qcom,glinkpkt-edge";
ret = of_property_read_string(np, key, &gpdev->edge);
if (ret < 0)
goto error;
key = "qcom,glinkpkt-ch-name";
ret = of_property_read_string(np, key, &gpdev->ch_name);
if (ret < 0)
goto error;
key = "qcom,glinkpkt-dev-name";
ret = of_property_read_string(np, key, &gpdev->dev_name);
if (ret < 0)
goto error;
key = "qcom,glinkpkt-enable-ch-close";
gpdev->enable_ch_close = of_property_read_bool(np, key);
key = "qcom,glinkpkt-fragmented-read";
gpdev->fragmented_read = of_property_read_bool(np, key);
GLINK_PKT_INFO(
"Parsed %s:%s /dev/%s enable channel close:%d fragmented-read:%d\n",
gpdev->edge, gpdev->ch_name, gpdev->dev_name,
gpdev->enable_ch_close, gpdev->fragmented_read);
return 0;
error:
GLINK_PKT_ERR("%s: missing key: %s\n", __func__, key);
return ret;
}
static void glink_pkt_release_device(struct device *dev)
{
struct glink_pkt_device *gpdev = dev_to_gpdev(dev);
GLINK_PKT_INFO("for %s by %s:%d ref_cnt[%d]\n",
gpdev->ch_name, current->comm,
task_pid_nr(current), refcount_read(&gpdev->refcount));
ida_simple_remove(&glink_pkt_minor_ida, MINOR(gpdev->dev.devt));
cdev_del(&gpdev->cdev);
kfree(gpdev);
}
static int glink_pkt_init_rpmsg(struct glink_pkt_device *gpdev)
{
struct rpmsg_driver *rpdrv = &gpdev->drv;
struct device *dev = &gpdev->dev;
struct rpmsg_device_id *match;
char *drv_name;
match = devm_kzalloc(dev, sizeof(*match) * 2, GFP_KERNEL);
if (!match)
return -ENOMEM;
strscpy(match->name, gpdev->ch_name, RPMSG_NAME_SIZE);
drv_name = devm_kasprintf(dev, GFP_KERNEL,
"%s_%s", "glink_pkt", gpdev->dev_name);
if (!drv_name)
return -ENOMEM;
rpdrv->probe = glink_pkt_rpdev_probe;
rpdrv->remove = glink_pkt_rpdev_remove;
rpdrv->callback = glink_pkt_rpdev_cb;
rpdrv->signals = glink_pkt_rpdev_sigs;
rpdrv->id_table = match;
rpdrv->drv.name = drv_name;
register_rpmsg_driver(rpdrv);
return 0;
}
/**
* glink_pkt_add_device() - Create glink packet device and add cdev
* parent: pointer to the parent device of this glink packet device
* np: pointer to device node this glink packet device represents
*
* return: 0 for success, Standard Linux errors
*/
static int glink_pkt_create_device(struct device *parent,
struct device_node *np)
{
struct glink_pkt_device *gpdev;
struct device *dev;
int ret, minor;
gpdev = kzalloc(sizeof(*gpdev), GFP_KERNEL);
if (!gpdev)
return -ENOMEM;
minor = ida_simple_get(&glink_pkt_minor_ida, 0, num_glink_pkt_devs,
GFP_KERNEL);
if (minor < 0) {
kfree(gpdev);
return minor;
}
dev = &gpdev->dev;
ret = glink_pkt_parse_devicetree(np, gpdev);
if (ret < 0) {
GLINK_PKT_ERR("failed to parse dt ret:%d\n", ret);
ida_simple_remove(&glink_pkt_minor_ida, MINOR(dev->devt));
kfree(gpdev);
return ret;
}
mutex_init(&gpdev->lock);
refcount_set(&gpdev->refcount, 1);
init_completion(&gpdev->ch_open);
/* Default open timeout for open is 120 sec */
gpdev->open_tout = 120;
gpdev->sig_change = false;
spin_lock_init(&gpdev->queue_lock);
gpdev->rskb = NULL;
gpdev->rdata = NULL;
gpdev->rdata_len = 0;
skb_queue_head_init(&gpdev->queue);
init_waitqueue_head(&gpdev->readq);
device_initialize(dev);
dev->class = glink_pkt_class;
dev->parent = parent;
dev->groups = glink_pkt_device_groups;
dev_set_drvdata(dev, gpdev);
cdev_init(&gpdev->cdev, &glink_pkt_fops);
gpdev->cdev.owner = THIS_MODULE;
dev->devt = MKDEV(MAJOR(glink_pkt_major), minor);
dev_set_name(dev, gpdev->dev_name, minor);
ret = cdev_add(&gpdev->cdev, dev->devt, 1);
if (ret) {
GLINK_PKT_ERR("cdev_add failed for %s ret:%d\n",
gpdev->dev_name, ret);
ida_simple_remove(&glink_pkt_minor_ida, MINOR(dev->devt));
kfree(gpdev);
return ret;
}
dev->release = glink_pkt_release_device;
ret = device_add(dev);
if (ret) {
GLINK_PKT_ERR("device_create failed for %s ret:%d\n",
gpdev->dev_name, ret);
goto free_dev;
}
if (device_create_file(dev, &dev_attr_open_timeout))
GLINK_PKT_ERR("device_create_file failed for %s\n",
gpdev->dev_name);
if (glink_pkt_init_rpmsg(gpdev)) {
gpdev->drv_registered = 1;
goto free_dev;
}
return 0;
free_dev:
put_device(dev);
return ret;
}
/**
* glink_pkt_deinit() - De-initialize this module
*
* This function frees all the memory and unregisters the char device region.
*/
static void glink_pkt_deinit(void)
{
class_destroy(glink_pkt_class);
unregister_chrdev_region(MAJOR(glink_pkt_major), num_glink_pkt_devs);
}
/**
* glink_pkt_probe() - Probe a GLINK packet device
*
* pdev: Pointer to platform device.
*
* return: 0 on success, standard Linux error codes on error.
*
* This function is called when the underlying device tree driver registers
* a platform device, mapped to a G-Link packet device.
*/
static int glink_pkt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *cn;
int ret;
num_glink_pkt_devs = of_get_child_count(dev->of_node);
ret = alloc_chrdev_region(&glink_pkt_major, 0, num_glink_pkt_devs,
"glinkpkt");
if (ret < 0) {
GLINK_PKT_ERR("alloc_chrdev_region failed ret:%d\n", ret);
return ret;
}
glink_pkt_class = class_create(THIS_MODULE, "glinkpkt");
if (IS_ERR(glink_pkt_class)) {
ret = PTR_ERR(glink_pkt_class);
GLINK_PKT_ERR("class_create failed ret:%d\n", ret);
goto error_deinit;
}
for_each_child_of_node(dev->of_node, cn) {
glink_pkt_create_device(dev, cn);
}
GLINK_PKT_INFO("G-Link Packet Port Driver Initialized\n");
return 0;
error_deinit:
glink_pkt_deinit();
return ret;
}
static const struct of_device_id glink_pkt_match_table[] = {
{ .compatible = "qcom,glinkpkt" },
{},
};
static struct platform_driver glink_pkt_driver = {
.probe = glink_pkt_probe,
.driver = {
.name = MODULE_NAME,
.of_match_table = glink_pkt_match_table,
},
};
/**
* glink_pkt_init() - Initialization function for this module
*
* returns: 0 on success, standard Linux error code otherwise.
*/
static int __init glink_pkt_init(void)
{
int ret;
ret = platform_driver_register(&glink_pkt_driver);
if (ret) {
GLINK_PKT_ERR("%s: glink_pkt register failed %d\n",
__func__, ret);
return ret;
}
glink_pkt_ilctxt = ipc_log_context_create(GLINK_PKT_IPC_LOG_PAGE_CNT,
"glink_pkt", 0);
return 0;
}
/**
* glink_pkt_exit() - Exit function for this module
*
* This function is used to cleanup the module during the exit.
*/
static void __exit glink_pkt_exit(void)
{
glink_pkt_deinit();
platform_driver_unregister(&glink_pkt_driver);
}
module_init(glink_pkt_init);
module_exit(glink_pkt_exit);
MODULE_DESCRIPTION("MSM G-Link Packet Port");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */
#include <linux/of.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/remoteproc/qcom_rproc.h>
#include <linux/rpmsg/qcom_glink.h>
#include <linux/rpmsg.h>
#include <linux/ipc_logging.h>
#include <trace/events/rproc_qcom.h>
#define GLINK_PROBE_LOG_PAGE_CNT 4
static void *glink_ilc;
#define GLINK_INFO(x, ...) ipc_log_string(glink_ilc, x, ##__VA_ARGS__)
#define GLINK_ERR(dev, x, ...) \
do { \
dev_err(dev, "[%s]: "x, __func__, ##__VA_ARGS__); \
ipc_log_string(glink_ilc, "[%s]: "x, __func__, ##__VA_ARGS__); \
} while (0)
extern const struct dev_pm_ops glink_native_pm_ops;
struct edge_info {
struct list_head list;
struct device *dev;
struct device_node *node;
const char *glink_label;
const char *ssr_label;
void *glink;
int (*register_fn)(struct edge_info *einfo);
void (*unregister_fn)(struct edge_info *einfo);
struct notifier_block nb;
void *notifier_handle;
};
static LIST_HEAD(edge_infos);
static int glink_probe_ssr_cb(struct notifier_block *this,
unsigned long code, void *data)
{
struct edge_info *einfo = container_of(this, struct edge_info, nb);
GLINK_INFO("received %ld for %s\n", code, einfo->ssr_label);
switch (code) {
case QCOM_SSR_AFTER_POWERUP:
trace_rproc_qcom_event(dev_name(einfo->dev),
"QCOM_SSR_AFTER_POWERUP", "glink_probe_ssr-enter");
einfo->register_fn(einfo);
break;
case QCOM_SSR_AFTER_SHUTDOWN:
trace_rproc_qcom_event(dev_name(einfo->dev),
"QCOM_SSR_AFTER_SHUTDOWN", "glink_probe_ssr-enter");
einfo->unregister_fn(einfo);
break;
default:
break;
}
trace_rproc_qcom_event(dev_name(einfo->dev), "glink_probe_ssr", "exit");
return NOTIFY_DONE;
}
static int glink_probe_smem_reg(struct edge_info *einfo)
{
struct device *dev = einfo->dev;
einfo->glink = qcom_glink_smem_register(dev, einfo->node);
if (IS_ERR_OR_NULL(einfo->glink)) {
GLINK_ERR(dev, "register failed for %s\n", einfo->ssr_label);
einfo->glink = NULL;
}
GLINK_INFO("register successful for %s\n", einfo->ssr_label);
return 0;
}
static void glink_probe_smem_unreg(struct edge_info *einfo)
{
if (einfo->glink)
qcom_glink_smem_unregister(einfo->glink);
einfo->glink = NULL;
GLINK_INFO("unregister for %s\n", einfo->ssr_label);
}
static int glink_probe_spss_reg(struct edge_info *einfo)
{
struct device *dev = einfo->dev;
einfo->glink = qcom_glink_spss_register(dev, einfo->node);
if (IS_ERR_OR_NULL(einfo->glink)) {
GLINK_ERR(dev, "register failed for %s\n", einfo->ssr_label);
einfo->glink = NULL;
}
return 0;
}
static void glink_probe_spss_unreg(struct edge_info *einfo)
{
if (einfo->glink)
qcom_glink_spss_unregister(einfo->glink);
einfo->glink = NULL;
}
static void probe_subsystem(struct device *dev, struct device_node *np)
{
struct edge_info *einfo;
const char *transport;
int ret;
void *handle;
einfo = devm_kzalloc(dev, sizeof(*einfo), GFP_KERNEL);
if (!einfo)
return;
ret = of_property_read_string(np, "label", &einfo->ssr_label);
if (ret < 0)
einfo->ssr_label = np->name;
ret = of_property_read_string(np, "qcom,glink-label",
&einfo->glink_label);
if (ret < 0) {
GLINK_ERR(dev, "no qcom,glink-label for %s\n",
einfo->ssr_label);
return;
}
einfo->dev = dev;
einfo->node = np;
ret = of_property_read_string(np, "transport", &transport);
if (ret < 0) {
GLINK_ERR(dev, "%s missing transport\n", einfo->ssr_label);
return;
}
if (!strcmp(transport, "smem")) {
einfo->register_fn = glink_probe_smem_reg;
einfo->unregister_fn = glink_probe_smem_unreg;
} else if (!strcmp(transport, "spss")) {
einfo->register_fn = glink_probe_spss_reg;
einfo->unregister_fn = glink_probe_spss_unreg;
}
einfo->nb.notifier_call = glink_probe_ssr_cb;
handle = qcom_register_ssr_notifier(einfo->ssr_label, &einfo->nb);
if (IS_ERR_OR_NULL(handle)) {
GLINK_ERR(dev, "could not register for SSR notifier for %s\n",
einfo->ssr_label);
return;
}
einfo->notifier_handle = handle;
list_add_tail(&einfo->list, &edge_infos);
GLINK_INFO("probe successful for %s\n", einfo->ssr_label);
}
static int glink_probe(struct platform_device *pdev)
{
struct device_node *pn = pdev->dev.of_node;
struct device_node *cn;
for_each_available_child_of_node(pn, cn) {
probe_subsystem(&pdev->dev, cn);
}
return 0;
}
static int glink_remove(struct platform_device *pdev)
{
struct device_node *pn = pdev->dev.of_node;
struct device_node *cn;
struct edge_info *einfo, *tmp;
for_each_available_child_of_node(pn, cn) {
list_for_each_entry_safe(einfo, tmp, &edge_infos, list) {
qcom_unregister_ssr_notifier(einfo->notifier_handle,
&einfo->nb);
list_del(&einfo->list);
}
}
return 0;
}
static const struct of_device_id glink_match_table[] = {
{ .compatible = "qcom,glink" },
{},
};
static struct platform_driver glink_probe_driver = {
.probe = glink_probe,
.remove = glink_remove,
.driver = {
.name = "msm_glink",
.of_match_table = glink_match_table,
.pm = &glink_native_pm_ops,
},
};
static int __init glink_probe_init(void)
{
int ret;
glink_ilc = ipc_log_context_create(GLINK_PROBE_LOG_PAGE_CNT,
"glink_probe", 0);
ret = platform_driver_register(&glink_probe_driver);
if (ret) {
pr_err("%s: glink_probe register failed %d\n",
__func__, ret);
return ret;
}
return 0;
}
arch_initcall(glink_probe_init);
static void __exit glink_probe_exit(void)
{
if (glink_ilc)
ipc_log_context_destroy(glink_ilc);
platform_driver_unregister(&glink_probe_driver);
}
module_exit(glink_probe_exit);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. GLINK probe helper driver");
MODULE_LICENSE("GPL");