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:
Sarannya S 2019-12-23 13:14:14 +05:30
parent 6897f46d97
commit 393dbf987b
4 changed files with 940 additions and 0 deletions

View File

@ -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"

View File

@ -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
View 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");

View 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 */