diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 8b20924cea45..a8c43ca4a6d7 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -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 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index a81f7f58b2d1..c324f0d4e817 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -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 diff --git a/drivers/soc/qcom/glink_pkt.c b/drivers/soc/qcom/glink_pkt.c new file mode 100644 index 000000000000..e6c9c8dead71 --- /dev/null +++ b/drivers/soc/qcom/glink_pkt.c @@ -0,0 +1,938 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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"); diff --git a/drivers/soc/qcom/glink_probe.c b/drivers/soc/qcom/glink_probe.c new file mode 100644 index 000000000000..cff1068084f3 --- /dev/null +++ b/drivers/soc/qcom/glink_probe.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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");