kernel: driver: Added support msm_smd_pkt driver
msm_smd_pkt driver provides data communication to remote peripherals such as Modem over RPMSG. Change-Id: I8d05786214620e11fb6b372b9b28ba70d5fecc4e Signed-off-by: Santosh Kumar M <santhoshm@codeaurora.org> Signed-off-by: Sivasri Kumar Vanka <sivasri@codeaurora.org> Signed-off-by: Sarannya S <sarannya@codeaurora.org>
This commit is contained in:
parent
6897f46d97
commit
393dbf987b
@ -538,6 +538,27 @@ config DEVPORT
|
||||
|
||||
source "drivers/s390/char/Kconfig"
|
||||
|
||||
config MSM_SMD_PKT
|
||||
bool "Enable device interface for some SMD packet ports"
|
||||
default n
|
||||
depends on RPMSG_QCOM_SMD
|
||||
help
|
||||
smd_pkt driver provides the interface for the userspace clients
|
||||
to communicate over smd via device nodes. This enable the
|
||||
usersapce clients to read and write to some smd packets channel
|
||||
for MSM chipset.
|
||||
|
||||
config TILE_SROM
|
||||
tristate "Character-device access via hypervisor to the Tilera SPI ROM"
|
||||
depends on TILE
|
||||
default y
|
||||
help
|
||||
This device provides character-level read-write access
|
||||
to the SROM, typically via the "0", "1", and "2" devices
|
||||
in /dev/srom/. The Tilera hypervisor makes the flash
|
||||
device appear much like a simple EEPROM, and knows
|
||||
how to partition a single ROM for multiple purposes.
|
||||
|
||||
source "drivers/char/xillybus/Kconfig"
|
||||
source "drivers/char/diag/Kconfig"
|
||||
|
||||
|
@ -14,6 +14,8 @@ obj-$(CONFIG_MSPEC) += mspec.o
|
||||
obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o
|
||||
obj-$(CONFIG_IBM_BSR) += bsr.o
|
||||
obj-$(CONFIG_SGI_MBCS) += mbcs.o
|
||||
obj-$(CONFIG_BFIN_OTP) += bfin-otp.o
|
||||
obj-$(CONFIG_MSM_SMD_PKT) += msm_smd_pkt.o
|
||||
|
||||
obj-$(CONFIG_PRINTER) += lp.o
|
||||
|
||||
|
893
drivers/char/msm_smd_pkt.c
Normal file
893
drivers/char/msm_smd_pkt.c
Normal file
@ -0,0 +1,893 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2019, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#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>
|
||||
#include <linux/msm_smd_pkt.h>
|
||||
|
||||
#define MODULE_NAME "msm_smdpkt"
|
||||
#define DEVICE_NAME "smdpkt"
|
||||
#define SMD_PKT_IPC_LOG_PAGE_CNT 2
|
||||
|
||||
/**
|
||||
* struct smd_pkt - driver context, relates rpdev to cdev
|
||||
* @dev: smd pkt device
|
||||
* @cdev: cdev for the smd 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 smd 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
|
||||
* @dev_name: /dev/@dev_name for smd_pkt device
|
||||
* @ch_name: smd channel to match to
|
||||
* @edge: smd edge to match to
|
||||
* @open_tout: timeout for open syscall, configurable in sysfs
|
||||
*/
|
||||
struct smd_pkt_dev {
|
||||
|
||||
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;
|
||||
const char *dev_name;
|
||||
const char *ch_name;
|
||||
const char *edge;
|
||||
int open_tout;
|
||||
};
|
||||
|
||||
#define dev_to_smd_pkt_devp(_dev) container_of(_dev, struct smd_pkt_dev, dev)
|
||||
#define cdev_to_smd_pkt_devp(_cdev) container_of(_cdev, \
|
||||
struct smd_pkt_dev, cdev)
|
||||
#define drv_to_rpdrv(_drv) container_of(_drv, struct rpmsg_driver, drv)
|
||||
#define rpdrv_to_smd_pkt_devp(_rdrv) container_of(_rdrv, \
|
||||
struct smd_pkt_dev, drv)
|
||||
|
||||
static void *smd_pkt_ilctxt;
|
||||
|
||||
static int smd_pkt_debug_mask;
|
||||
|
||||
module_param_named(debug_mask, smd_pkt_debug_mask, int, 0664);
|
||||
|
||||
enum {
|
||||
SMD_PKT_INFO = 1U << 0,
|
||||
};
|
||||
|
||||
#define SMD_PKT_INFO(x, ...) \
|
||||
do { \
|
||||
if (smd_pkt_debug_mask & SMD_PKT_INFO) { \
|
||||
ipc_log_string(smd_pkt_ilctxt, \
|
||||
"[%s]: "x, __func__, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SMD_PKT_ERR(x, ...) \
|
||||
do { \
|
||||
pr_err_ratelimited("[%s]: "x, __func__, ##__VA_ARGS__); \
|
||||
ipc_log_string(smd_pkt_ilctxt, "[%s]: "x, __func__, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define SMD_PKT_IOCTL_QUEUE_RX_INTENT \
|
||||
_IOW(SMD_PKT_IOCTL_MAGIC, 0, unsigned int)
|
||||
|
||||
static dev_t smd_pkt_major;
|
||||
static struct class *smd_pkt_class;
|
||||
static int num_smd_pkt_devs;
|
||||
|
||||
static DEFINE_IDA(smd_pkt_minor_ida);
|
||||
static ssize_t open_timeout_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t n)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = dev_to_smd_pkt_devp(dev);
|
||||
long tmp;
|
||||
|
||||
mutex_lock(&smd_pkt_devp->lock);
|
||||
if (kstrtol(buf, 0, &tmp)) {
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
SMD_PKT_ERR("unable to convert:%s to an int for /dev/%s\n",
|
||||
buf, smd_pkt_devp->dev_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
smd_pkt_devp->open_tout = tmp;
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static ssize_t open_timeout_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = dev_to_smd_pkt_devp(dev);
|
||||
ssize_t ret;
|
||||
|
||||
mutex_lock(&smd_pkt_devp->lock);
|
||||
ret = scnprintf(buf, PAGE_SIZE, "%d\n", smd_pkt_devp->open_tout);
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(open_timeout, 0644, open_timeout_show,
|
||||
open_timeout_store);
|
||||
|
||||
static int smd_pkt_rpdev_probe(struct rpmsg_device *rpdev)
|
||||
{
|
||||
struct device_driver *drv = rpdev->dev.driver;
|
||||
struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv);
|
||||
struct smd_pkt_dev *smd_pkt_devp = rpdrv_to_smd_pkt_devp(rpdrv);
|
||||
|
||||
mutex_lock(&smd_pkt_devp->lock);
|
||||
smd_pkt_devp->rpdev = rpdev;
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
dev_set_drvdata(&rpdev->dev, smd_pkt_devp);
|
||||
complete_all(&smd_pkt_devp->ch_open);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smd_pkt_rpdev_cb(struct rpmsg_device *rpdev, void *buf, int len,
|
||||
void *priv, u32 addr)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = dev_get_drvdata(&rpdev->dev);
|
||||
unsigned long flags;
|
||||
struct sk_buff *skb;
|
||||
|
||||
skb = alloc_skb(len, GFP_ATOMIC);
|
||||
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
|
||||
skb_put_data(skb, buf, len);
|
||||
|
||||
spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags);
|
||||
|
||||
skb_queue_tail(&smd_pkt_devp->queue, skb);
|
||||
spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags);
|
||||
|
||||
/* wake up any blocking processes, waiting for new data */
|
||||
wake_up_interruptible(&smd_pkt_devp->readq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smd_pkt_rpdev_sigs(struct rpmsg_device *rpdev, u32 old, u32 new)
|
||||
{
|
||||
struct device_driver *drv = rpdev->dev.driver;
|
||||
struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv);
|
||||
struct smd_pkt_dev *smd_pkt_devp = rpdrv_to_smd_pkt_devp(rpdrv);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags);
|
||||
smd_pkt_devp->sig_change = true;
|
||||
spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags);
|
||||
|
||||
/* wake up any blocking processes, waiting for new data */
|
||||
wake_up_interruptible(&smd_pkt_devp->readq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_tiocmset() - set the signals for smd_pkt device
|
||||
* smd_pkt_devp: Pointer to the smd_pkt device structure.
|
||||
* cmd: IOCTL command.
|
||||
* arg: Arguments to the ioctl call.
|
||||
*
|
||||
* This function is used to set the signals on the smd pkt device
|
||||
* when userspace client do a ioctl() system call with TIOCMBIS,
|
||||
* TIOCMBIC and TICOMSET.
|
||||
*/
|
||||
static int smd_pkt_tiocmset(struct smd_pkt_dev *smd_pkt_devp, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
u32 lsigs, rsigs, val;
|
||||
int ret;
|
||||
|
||||
ret = get_user(val, (u32 *)arg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = rpmsg_get_sigs(smd_pkt_devp->rpdev->ept, &lsigs, &rsigs);
|
||||
if (ret < 0) {
|
||||
SMD_PKT_ERR("Get signals failed[%d]\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case TIOCMBIS:
|
||||
lsigs |= val;
|
||||
break;
|
||||
case TIOCMBIC:
|
||||
lsigs &= ~val;
|
||||
break;
|
||||
case TIOCMSET:
|
||||
lsigs = val;
|
||||
break;
|
||||
}
|
||||
|
||||
return rpmsg_set_sigs(smd_pkt_devp->rpdev->ept, lsigs);
|
||||
SMD_PKT_INFO("sigs[0x%x] ret[%d]\n", lsigs, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_ioctl() - ioctl() syscall for the smd_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 smd 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 smd_pkt_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp;
|
||||
unsigned long flags;
|
||||
u32 lsigs, rsigs;
|
||||
int ret;
|
||||
|
||||
smd_pkt_devp = file->private_data;
|
||||
|
||||
if (!smd_pkt_devp || refcount_read(&smd_pkt_devp->refcount) == 1) {
|
||||
SMD_PKT_ERR("invalid device handle\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (mutex_lock_interruptible(&smd_pkt_devp->lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (!completion_done(&smd_pkt_devp->ch_open)) {
|
||||
SMD_PKT_ERR("%s channel in reset\n", smd_pkt_devp->ch_name);
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
return -ENETRESET;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case TIOCMGET:
|
||||
spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags);
|
||||
smd_pkt_devp->sig_change = false;
|
||||
spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags);
|
||||
|
||||
ret = rpmsg_get_sigs(smd_pkt_devp->rpdev->ept, &lsigs, &rsigs);
|
||||
if (!ret)
|
||||
ret = put_user(rsigs, (uint32_t *)arg);
|
||||
break;
|
||||
case TIOCMSET:
|
||||
case TIOCMBIS:
|
||||
case TIOCMBIC:
|
||||
ret = smd_pkt_tiocmset(smd_pkt_devp, cmd, arg);
|
||||
break;
|
||||
case SMD_PKT_IOCTL_QUEUE_RX_INTENT:
|
||||
/* Return success to not break userspace client logic */
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
SMD_PKT_ERR("unrecognized ioctl command 0x%x\n", cmd);
|
||||
ret = -ENOIOCTLCMD;
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_read() - read() syscall for the smd_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 smd pkt device when
|
||||
* userspace client do a read() system call. All input arguments are
|
||||
* validated by the virtual file system before calling this function.
|
||||
*/
|
||||
ssize_t smd_pkt_read(struct file *file,
|
||||
char __user *buf,
|
||||
size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
|
||||
struct smd_pkt_dev *smd_pkt_devp = file->private_data;
|
||||
unsigned long flags;
|
||||
struct sk_buff *skb;
|
||||
int use;
|
||||
|
||||
if (!smd_pkt_devp ||
|
||||
refcount_read(&smd_pkt_devp->refcount) == 1) {
|
||||
SMD_PKT_ERR("invalid device handle\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!completion_done(&smd_pkt_devp->ch_open)) {
|
||||
SMD_PKT_ERR("%s channel in reset\n", smd_pkt_devp->ch_name);
|
||||
return -ENETRESET;
|
||||
}
|
||||
|
||||
SMD_PKT_INFO("begin for %s by %s:%d ref_cnt[%d]\n",
|
||||
smd_pkt_devp->ch_name, current->comm,
|
||||
task_pid_nr(current),
|
||||
refcount_read(&smd_pkt_devp->refcount));
|
||||
|
||||
spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags);
|
||||
|
||||
/* Wait for data in the queue */
|
||||
if (skb_queue_empty(&smd_pkt_devp->queue)) {
|
||||
spin_unlock_irqrestore(&smd_pkt_devp->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(smd_pkt_devp->readq,
|
||||
!skb_queue_empty(&smd_pkt_devp->queue) ||
|
||||
!completion_done(&smd_pkt_devp->ch_open)))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags);
|
||||
}
|
||||
|
||||
skb = skb_dequeue(&smd_pkt_devp->queue);
|
||||
spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags);
|
||||
if (!skb)
|
||||
return -EFAULT;
|
||||
|
||||
use = min_t(size_t, count, skb->len);
|
||||
if (copy_to_user(buf, skb->data, use))
|
||||
use = -EFAULT;
|
||||
|
||||
kfree_skb(skb);
|
||||
|
||||
SMD_PKT_INFO("end for %s by %s:%d ret[%d]\n", smd_pkt_devp->ch_name,
|
||||
current->comm, task_pid_nr(current), use);
|
||||
|
||||
return use;
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_write() - write() syscall for the smd_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 smd pkt device when
|
||||
* userspace client do a write() system call. All input arguments are
|
||||
* validated by the virtual file system before calling this function.
|
||||
*/
|
||||
ssize_t smd_pkt_write(struct file *file,
|
||||
const char __user *buf,
|
||||
size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = file->private_data;
|
||||
void *kbuf;
|
||||
int ret;
|
||||
|
||||
smd_pkt_devp = file->private_data;
|
||||
|
||||
if (!smd_pkt_devp || refcount_read(&smd_pkt_devp->refcount) == 1) {
|
||||
SMD_PKT_ERR("invalid device handle\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
SMD_PKT_INFO("begin to %s buffer_size %zu\n",
|
||||
smd_pkt_devp->ch_name, count);
|
||||
kbuf = memdup_user(buf, count);
|
||||
if (IS_ERR(kbuf))
|
||||
return PTR_ERR(kbuf);
|
||||
|
||||
if (mutex_lock_interruptible(&smd_pkt_devp->lock)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto free_kbuf;
|
||||
}
|
||||
|
||||
if (!completion_done(&smd_pkt_devp->ch_open) ||
|
||||
!smd_pkt_devp->rpdev) {
|
||||
SMD_PKT_ERR("%s channel in reset\n", smd_pkt_devp->ch_name);
|
||||
ret = -ENETRESET;
|
||||
goto unlock_ch;
|
||||
}
|
||||
|
||||
if (file->f_flags & O_NONBLOCK)
|
||||
ret = rpmsg_trysend(smd_pkt_devp->rpdev->ept, kbuf, count);
|
||||
else
|
||||
ret = rpmsg_send(smd_pkt_devp->rpdev->ept, kbuf, count);
|
||||
|
||||
unlock_ch:
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
|
||||
free_kbuf:
|
||||
kfree(kbuf);
|
||||
SMD_PKT_INFO("finish to %s ret %d\n", smd_pkt_devp->ch_name, ret);
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_poll() - poll() syscall for the smd_pkt device
|
||||
* file: Pointer to the file structure.
|
||||
* wait: pointer to Poll table.
|
||||
*
|
||||
* This function is used to poll on the smd 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 unsigned int smd_pkt_poll(struct file *file, poll_table *wait)
|
||||
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = file->private_data;
|
||||
unsigned int mask = 0;
|
||||
unsigned long flags;
|
||||
|
||||
smd_pkt_devp = file->private_data;
|
||||
|
||||
if (!smd_pkt_devp || refcount_read(&smd_pkt_devp->refcount) == 1) {
|
||||
SMD_PKT_ERR("invalid device handle\n");
|
||||
return POLLERR;
|
||||
}
|
||||
|
||||
if (!completion_done(&smd_pkt_devp->ch_open)) {
|
||||
SMD_PKT_ERR("%s channel in reset\n", smd_pkt_devp->ch_name);
|
||||
return POLLHUP;
|
||||
}
|
||||
|
||||
poll_wait(file, &smd_pkt_devp->readq, wait);
|
||||
|
||||
mutex_lock(&smd_pkt_devp->lock);
|
||||
|
||||
if (!completion_done(&smd_pkt_devp->ch_open) ||
|
||||
!smd_pkt_devp->rpdev) {
|
||||
SMD_PKT_ERR("%s channel reset after wait\n",
|
||||
smd_pkt_devp->ch_name);
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
return POLLHUP;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags);
|
||||
if (!skb_queue_empty(&smd_pkt_devp->queue))
|
||||
mask |= POLLIN | POLLRDNORM;
|
||||
|
||||
if (smd_pkt_devp->sig_change)
|
||||
mask |= POLLPRI;
|
||||
spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags);
|
||||
|
||||
mask |= rpmsg_poll(smd_pkt_devp->rpdev->ept, file, wait);
|
||||
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static void smd_pkt_rpdev_remove(struct rpmsg_device *rpdev)
|
||||
{
|
||||
struct device_driver *drv = rpdev->dev.driver;
|
||||
struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv);
|
||||
struct smd_pkt_dev *smd_pkt_devp = rpdrv_to_smd_pkt_devp(rpdrv);
|
||||
|
||||
mutex_lock(&smd_pkt_devp->lock);
|
||||
smd_pkt_devp->rpdev = NULL;
|
||||
mutex_unlock(&smd_pkt_devp->lock);
|
||||
|
||||
dev_set_drvdata(&rpdev->dev, NULL);
|
||||
|
||||
/* wake up any blocked readers */
|
||||
reinit_completion(&smd_pkt_devp->ch_open);
|
||||
wake_up_interruptible(&smd_pkt_devp->readq);
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_open() - open() syscall for the smd_pkt device
|
||||
* inode: Pointer to the inode structure.
|
||||
* file: Pointer to the file structure.
|
||||
*
|
||||
* This function is used to open the smd pkt device when
|
||||
* userspace client do a open() system call. All input arguments are
|
||||
* validated by the virtual file system before calling this function.
|
||||
*/
|
||||
int smd_pkt_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = cdev_to_smd_pkt_devp(inode->i_cdev);
|
||||
int tout = msecs_to_jiffies(smd_pkt_devp->open_tout * 1000);
|
||||
struct device *dev = &smd_pkt_devp->dev;
|
||||
int ret;
|
||||
|
||||
refcount_inc(&smd_pkt_devp->refcount);
|
||||
get_device(dev);
|
||||
|
||||
SMD_PKT_INFO("begin for %s by %s:%d ref_cnt[%d]\n",
|
||||
smd_pkt_devp->ch_name, current->comm,
|
||||
task_pid_nr(current),
|
||||
refcount_read(&smd_pkt_devp->refcount));
|
||||
|
||||
ret = wait_for_completion_interruptible_timeout(&smd_pkt_devp->ch_open,
|
||||
tout);
|
||||
if (ret <= 0) {
|
||||
refcount_dec(&smd_pkt_devp->refcount);
|
||||
put_device(dev);
|
||||
SMD_PKT_INFO("timeout for %s by %s:%d\n", smd_pkt_devp->ch_name,
|
||||
current->comm, task_pid_nr(current));
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
file->private_data = smd_pkt_devp;
|
||||
|
||||
SMD_PKT_INFO("end for %s by %s:%d ref_cnt[%d]\n",
|
||||
smd_pkt_devp->ch_name, current->comm,
|
||||
task_pid_nr(current),
|
||||
refcount_read(&smd_pkt_devp->refcount));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_release() - release operation on smd_pkt device
|
||||
* inode: Pointer to the inode structure.
|
||||
* file: Pointer to the file structure.
|
||||
*
|
||||
* This function is used to release the smd pkt device when
|
||||
* userspace client do a close() system call. All input arguments are
|
||||
* validated by the virtual file system before calling this function.
|
||||
*/
|
||||
int smd_pkt_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = cdev_to_smd_pkt_devp(inode->i_cdev);
|
||||
struct device *dev = &smd_pkt_devp->dev;
|
||||
struct sk_buff *skb;
|
||||
unsigned long flags;
|
||||
|
||||
SMD_PKT_INFO("for %s by %s:%d ref_cnt[%d]\n",
|
||||
smd_pkt_devp->ch_name, current->comm,
|
||||
task_pid_nr(current),
|
||||
refcount_read(&smd_pkt_devp->refcount));
|
||||
|
||||
refcount_dec(&smd_pkt_devp->refcount);
|
||||
if (refcount_read(&smd_pkt_devp->refcount) == 1) {
|
||||
spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags);
|
||||
|
||||
/* Discard all SKBs */
|
||||
while (!skb_queue_empty(&smd_pkt_devp->queue)) {
|
||||
skb = skb_dequeue(&smd_pkt_devp->queue);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
wake_up_interruptible(&smd_pkt_devp->readq);
|
||||
smd_pkt_devp->sig_change = false;
|
||||
spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags);
|
||||
}
|
||||
|
||||
put_device(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations smd_pkt_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = smd_pkt_open,
|
||||
.release = smd_pkt_release,
|
||||
.read = smd_pkt_read,
|
||||
.write = smd_pkt_write,
|
||||
.poll = smd_pkt_poll,
|
||||
.unlocked_ioctl = smd_pkt_ioctl,
|
||||
.compat_ioctl = smd_pkt_ioctl,
|
||||
};
|
||||
|
||||
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = dev_to_smd_pkt_devp(dev);
|
||||
|
||||
return scnprintf(buf, RPMSG_NAME_SIZE, "%s\n", smd_pkt_devp->ch_name);
|
||||
}
|
||||
static DEVICE_ATTR_RO(name);
|
||||
|
||||
static struct attribute *smd_pkt_device_attrs[] = {
|
||||
&dev_attr_name.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(smd_pkt_device);
|
||||
|
||||
/**
|
||||
* parse_smdpkt_devicetree() - parse device tree binding for a subnode
|
||||
*
|
||||
* np: pointer to a device tree node
|
||||
* smd_pkt_devp: pointer to SMD PACKET device
|
||||
*
|
||||
* Return: 0 on success, standard Linux error codes on error.
|
||||
*/
|
||||
static int smd_pkt_parse_devicetree(struct device_node *np,
|
||||
struct smd_pkt_dev *smd_pkt_devp)
|
||||
{
|
||||
char *key;
|
||||
int ret;
|
||||
|
||||
key = "qcom,smdpkt-edge";
|
||||
|
||||
ret = of_property_read_string(np, key, &smd_pkt_devp->edge);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
key = "qcom,smdpkt-ch-name";
|
||||
|
||||
ret = of_property_read_string(np, key, &smd_pkt_devp->ch_name);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
key = "qcom,smdpkt-dev-name";
|
||||
|
||||
ret = of_property_read_string(np, key, &smd_pkt_devp->dev_name);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
SMD_PKT_INFO("Parsed %s:%s /dev/%s\n", smd_pkt_devp->edge,
|
||||
smd_pkt_devp->ch_name,
|
||||
smd_pkt_devp->dev_name);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
SMD_PKT_ERR("missing key: %s\n", key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void smd_pkt_release_device(struct device *dev)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp = dev_to_smd_pkt_devp(dev);
|
||||
|
||||
ida_simple_remove(&smd_pkt_minor_ida, MINOR(smd_pkt_devp->dev.devt));
|
||||
cdev_del(&smd_pkt_devp->cdev);
|
||||
}
|
||||
|
||||
static int smd_pkt_init_rpmsg(struct smd_pkt_dev *smd_pkt_devp)
|
||||
{
|
||||
struct rpmsg_driver *rpdrv = &smd_pkt_devp->drv;
|
||||
struct device *dev = &smd_pkt_devp->dev;
|
||||
struct rpmsg_device_id *match;
|
||||
char *drv_name;
|
||||
|
||||
/* zalloc array of two to NULL terminate the match list */
|
||||
match = devm_kzalloc(dev, 2 * sizeof(*match), GFP_KERNEL);
|
||||
if (!match)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(match->name, RPMSG_NAME_SIZE, "%s", smd_pkt_devp->ch_name);
|
||||
|
||||
drv_name = devm_kasprintf(dev, GFP_KERNEL,
|
||||
"%s_%s", "msm_smd_pkt", smd_pkt_devp->dev_name);
|
||||
if (!drv_name)
|
||||
return -ENOMEM;
|
||||
|
||||
rpdrv->probe = smd_pkt_rpdev_probe;
|
||||
rpdrv->remove = smd_pkt_rpdev_remove;
|
||||
rpdrv->callback = smd_pkt_rpdev_cb;
|
||||
rpdrv->signals = smd_pkt_rpdev_sigs;
|
||||
rpdrv->id_table = match;
|
||||
rpdrv->drv.name = drv_name;
|
||||
|
||||
register_rpmsg_driver(rpdrv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* smdpkt - Create smd packet device and add cdev
|
||||
* parent: pointer to the parent device of this smd packet device
|
||||
* np: pointer to device node this smd packet device represents
|
||||
*
|
||||
* return: 0 for success, Standard Linux errors
|
||||
*/
|
||||
static int smd_pkt_create_device(struct device *parent,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct smd_pkt_dev *smd_pkt_devp;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
smd_pkt_devp = devm_kzalloc(parent, sizeof(*smd_pkt_devp), GFP_KERNEL);
|
||||
if (!smd_pkt_devp)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = smd_pkt_parse_devicetree(np, smd_pkt_devp);
|
||||
if (ret < 0) {
|
||||
SMD_PKT_ERR("failed to parse dt ret:%d\n", ret);
|
||||
goto free_smd_pkt_devp;
|
||||
}
|
||||
|
||||
dev = &smd_pkt_devp->dev;
|
||||
mutex_init(&smd_pkt_devp->lock);
|
||||
refcount_set(&smd_pkt_devp->refcount, 1);
|
||||
init_completion(&smd_pkt_devp->ch_open);
|
||||
|
||||
/* Default open timeout for open is 120 sec */
|
||||
smd_pkt_devp->open_tout = 120;
|
||||
smd_pkt_devp->sig_change = false;
|
||||
|
||||
spin_lock_init(&smd_pkt_devp->queue_lock);
|
||||
skb_queue_head_init(&smd_pkt_devp->queue);
|
||||
init_waitqueue_head(&smd_pkt_devp->readq);
|
||||
|
||||
device_initialize(dev);
|
||||
dev->class = smd_pkt_class;
|
||||
dev->parent = parent;
|
||||
dev->groups = smd_pkt_device_groups;
|
||||
dev_set_drvdata(dev, smd_pkt_devp);
|
||||
|
||||
cdev_init(&smd_pkt_devp->cdev, &smd_pkt_fops);
|
||||
smd_pkt_devp->cdev.owner = THIS_MODULE;
|
||||
ret = ida_simple_get(&smd_pkt_minor_ida, 0, num_smd_pkt_devs,
|
||||
GFP_KERNEL);
|
||||
if (ret < 0)
|
||||
goto free_dev;
|
||||
|
||||
dev->devt = MKDEV(MAJOR(smd_pkt_major), ret);
|
||||
dev_set_name(dev, smd_pkt_devp->dev_name, ret);
|
||||
|
||||
ret = cdev_add(&smd_pkt_devp->cdev, dev->devt, 1);
|
||||
if (ret) {
|
||||
SMD_PKT_ERR("cdev_add failed for %s ret:%d\n",
|
||||
smd_pkt_devp->dev_name, ret);
|
||||
goto free_minor_ida;
|
||||
}
|
||||
|
||||
dev->release = smd_pkt_release_device;
|
||||
ret = device_add(dev);
|
||||
if (ret) {
|
||||
SMD_PKT_ERR("device_create failed for %s ret:%d\n",
|
||||
smd_pkt_devp->dev_name, ret);
|
||||
goto free_minor_ida;
|
||||
}
|
||||
|
||||
if (device_create_file(dev, &dev_attr_open_timeout))
|
||||
SMD_PKT_ERR("device_create_file failed for %s\n",
|
||||
smd_pkt_devp->dev_name);
|
||||
|
||||
if (smd_pkt_init_rpmsg(smd_pkt_devp))
|
||||
goto free_minor_ida;
|
||||
|
||||
return 0;
|
||||
|
||||
free_minor_ida:
|
||||
ida_simple_remove(&smd_pkt_minor_ida, MINOR(dev->devt));
|
||||
free_dev:
|
||||
put_device(dev);
|
||||
|
||||
free_smd_pkt_devp:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_deinit() - De-initialize this module
|
||||
*
|
||||
* This function frees all the memory and unregisters the char device region.
|
||||
*/
|
||||
static void smd_pkt_deinit(void)
|
||||
{
|
||||
class_destroy(smd_pkt_class);
|
||||
unregister_chrdev_region(MAJOR(smd_pkt_major), num_smd_pkt_devs);
|
||||
}
|
||||
|
||||
/**
|
||||
* smd_pkt_probe() - Probe a SMD 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 SMD packet device.
|
||||
*/
|
||||
static int msm_smd_pkt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *cn;
|
||||
int ret;
|
||||
|
||||
num_smd_pkt_devs = of_get_child_count(dev->of_node);
|
||||
ret = alloc_chrdev_region(&smd_pkt_major, 0, num_smd_pkt_devs,
|
||||
"smdpkt");
|
||||
|
||||
if (ret < 0) {
|
||||
SMD_PKT_ERR("alloc_chrdev_region failed ret:%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
smd_pkt_class = class_create(THIS_MODULE, "smdpkt");
|
||||
if (IS_ERR(smd_pkt_class)) {
|
||||
SMD_PKT_ERR("class_create failed ret:%ld\n",
|
||||
PTR_ERR(smd_pkt_class));
|
||||
goto error_deinit;
|
||||
}
|
||||
|
||||
for_each_child_of_node(dev->of_node, cn)
|
||||
smd_pkt_create_device(dev, cn);
|
||||
|
||||
SMD_PKT_INFO("smd Packet Port Driver Initialized\n");
|
||||
return 0;
|
||||
|
||||
error_deinit:
|
||||
smd_pkt_deinit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id msm_smd_pkt_match_table[] = {
|
||||
{ .compatible = "qcom,smdpkt" },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver msm_smd_pkt_driver = {
|
||||
.probe = msm_smd_pkt_probe,
|
||||
.driver = {
|
||||
.name = MODULE_NAME,
|
||||
.of_match_table = msm_smd_pkt_match_table,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* smd_pkt_init() - Initialization function for this module
|
||||
*
|
||||
* returns: 0 on success, standard Linux error code otherwise.
|
||||
*/
|
||||
static int __init smd_pkt_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = platform_driver_register(&msm_smd_pkt_driver);
|
||||
if (rc) {
|
||||
SMD_PKT_ERR("msm_smd_pkt driver register failed %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
smd_pkt_ilctxt = ipc_log_context_create(SMD_PKT_IPC_LOG_PAGE_CNT,
|
||||
"smd_pkt", 0);
|
||||
return 0;
|
||||
}
|
||||
module_init(smd_pkt_init);
|
||||
|
||||
/**
|
||||
* smd_pkt_exit() - Exit function for this module
|
||||
*
|
||||
* This function is used to cleanup the module during the exit.
|
||||
*/
|
||||
static void __exit smd_pkt_exit(void)
|
||||
{
|
||||
smd_pkt_deinit();
|
||||
}
|
||||
module_exit(smd_pkt_exit);
|
||||
|
||||
MODULE_DESCRIPTION("MSM Shared Memory Packet Port");
|
||||
MODULE_LICENSE("GPL v2");
|
24
include/linux/msm_smd_pkt.h
Normal file
24
include/linux/msm_smd_pkt.h
Normal file
@ -0,0 +1,24 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0
|
||||
* Copyright (c) 2010,2017,2019, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
#ifndef __LINUX_MSM_SMD_PKT_H
|
||||
#define __LINUX_MSM_SMD_PKT_H
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#define SMD_PKT_IOCTL_MAGIC (0xC2)
|
||||
|
||||
#define SMD_PKT_IOCTL_BLOCKING_WRITE \
|
||||
_IOR(SMD_PKT_IOCTL_MAGIC, 0, unsigned int)
|
||||
|
||||
#endif /* __LINUX_MSM_SMD_PKT_H */
|
Loading…
Reference in New Issue
Block a user