From 176254e54007cdf9b54d4f491c8ad945592acc36 Mon Sep 17 00:00:00 2001 From: Santosh Kumar M Date: Mon, 23 Dec 2019 13:14:14 +0530 Subject: [PATCH] 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 Signed-off-by: Sivasri Kumar Vanka Signed-off-by: Sarannya S --- drivers/char/Kconfig | 21 + drivers/char/Makefile | 3 + drivers/char/msm_smd_pkt.c | 892 ++++++++++++++++++++++++++++++++++++ include/linux/msm_smd_pkt.h | 24 + 4 files changed, 940 insertions(+) create mode 100644 drivers/char/msm_smd_pkt.c create mode 100644 include/linux/msm_smd_pkt.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index fcb5c760ae0d..3c8d882ff14b 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -521,6 +521,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" config MSM_ADSPRPC diff --git a/drivers/char/Makefile b/drivers/char/Makefile index f378ca3e1ed0..219b2f6a3f54 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -12,6 +12,9 @@ obj-$(CONFIG_RAW_DRIVER) += raw.o 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 diff --git a/drivers/char/msm_smd_pkt.c b/drivers/char/msm_smd_pkt.c new file mode 100644 index 000000000000..34f722ccb28e --- /dev/null +++ b/drivers/char/msm_smd_pkt.c @@ -0,0 +1,892 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2019-2020, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/include/linux/msm_smd_pkt.h b/include/linux/msm_smd_pkt.h new file mode 100644 index 000000000000..b6c8a95e0938 --- /dev/null +++ b/include/linux/msm_smd_pkt.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2010,2017,2019-2020, 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 + +#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 */