Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8bfc3881 authored by Chris Lew's avatar Chris Lew
Browse files

rpmsg: glink: Add GLINK signal support for RPMSG



Add support to handle SMD signals to RPMSG over GLINK. SMD signals
mimic serial protocol signals to notify of ports opening and closing.
This change affects the glink_pkt, rpmsg core and glink drivers.

Change-Id: I0b09219c793d23c65bae8ca747fc480ae6db47a6
Signed-off-by: default avatarChris Lew <clew@codeaurora.org>
parent 44f2f7b0
Loading
Loading
Loading
Loading
+79 −0
Original line number Diff line number Diff line
@@ -214,6 +214,9 @@ struct glink_channel {
	int buf_offset;
	int buf_size;

	unsigned int lsigs;
	unsigned int rsigs;

	struct completion open_ack;
	struct completion open_req;

@@ -240,6 +243,7 @@ static const struct rpmsg_endpoint_ops glink_endpoint_ops;
#define RPM_CMD_TX_DATA_CONT		12
#define RPM_CMD_READ_NOTIF		13
#define RPM_CMD_RX_DONE_W_REUSE		14
#define RPM_CMD_SIGNALS			15

#define GLINK_FEATURE_INTENTLESS	BIT(1)

@@ -1021,6 +1025,54 @@ static int qcom_glink_rx_open_ack(struct qcom_glink *glink, unsigned int lcid)
	return 0;
}

/**
 * qcom_glink_send_signals() - convert a signal  cmd to wire format and transmit
 * @glink:	The transport to transmit on.
 * @channel:	The glink channel
 * @sigs:	The signals to encode.
 *
 * Return: 0 on success or standard Linux error code.
 */
static int qcom_glink_send_signals(struct qcom_glink *glink,
				   struct glink_channel *channel,
				   u32 sigs)
{
	struct glink_msg msg;

	msg.cmd = cpu_to_le16(RPM_CMD_SIGNALS);
	msg.param1 = cpu_to_le16(channel->lcid);
	msg.param2 = cpu_to_le32(sigs);

	GLINK_INFO(glink->ilc, "sigs:%d\n", sigs);
	return qcom_glink_tx(glink, &msg, sizeof(msg), NULL, 0, true);
}

static int qcom_glink_handle_signals(struct qcom_glink *glink,
				     unsigned int rcid, unsigned int signals)
{
	struct glink_channel *channel;
	unsigned long flags;
	u32 old;

	spin_lock_irqsave(&glink->idr_lock, flags);
	channel = idr_find(&glink->rcids, rcid);
	spin_unlock_irqrestore(&glink->idr_lock, flags);
	if (!channel) {
		dev_err(glink->dev, "signal for non-existing channel\n");
		return -EINVAL;
	}

	old = channel->rsigs;
	channel->rsigs = signals;

	if (channel->ept.sig_cb)
		channel->ept.sig_cb(channel->ept.rpdev, old, channel->rsigs);

	CH_INFO(channel, "old:%d new:%d\n", old, channel->rsigs);

	return 0;
}

static irqreturn_t qcom_glink_native_intr(int irq, void *data)
{
	struct qcom_glink *glink = data;
@@ -1082,6 +1134,10 @@ static irqreturn_t qcom_glink_native_intr(int irq, void *data)
			qcom_glink_handle_intent_req_ack(glink, param1, param2);
			qcom_glink_rx_advance(glink, ALIGN(sizeof(msg), 8));
			break;
		case RPM_CMD_SIGNALS:
			qcom_glink_handle_signals(glink, param1, param2);
			qcom_glink_rx_advance(glink, ALIGN(sizeof(msg), 8));
			break;
		default:
			dev_err(glink->dev, "unhandled rx cmd: %d\n", cmd);
			ret = -EINVAL;
@@ -1389,6 +1445,27 @@ static int qcom_glink_trysend(struct rpmsg_endpoint *ept, void *data, int len)
	return __qcom_glink_send(channel, data, len, false);
}

static int qcom_glink_get_sigs(struct rpmsg_endpoint *ept,
			       u32 *lsigs, u32 *rsigs)
{
	struct glink_channel *channel = to_glink_channel(ept);

	*lsigs = channel->lsigs;
	*rsigs = channel->rsigs;

	return 0;
}

static int qcom_glink_set_sigs(struct rpmsg_endpoint *ept, u32 sigs)
{
	struct glink_channel *channel = to_glink_channel(ept);
	struct qcom_glink *glink = channel->glink;

	channel->lsigs = sigs;

	return qcom_glink_send_signals(glink, channel, sigs);
}

/*
 * Finds the device_node for the glink child interested in this channel.
 */
@@ -1426,6 +1503,8 @@ static const struct rpmsg_endpoint_ops glink_endpoint_ops = {
	.destroy_ept = qcom_glink_destroy_ept,
	.send = qcom_glink_send,
	.trysend = qcom_glink_trysend,
	.get_sigs = qcom_glink_get_sigs,
	.set_sigs = qcom_glink_set_sigs,
};

static void qcom_glink_rpdev_release(struct device *dev)
+41 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
 *
 * Copyright (C) 2011 Texas Instruments, Inc.
 * Copyright (C) 2011 Google, Inc.
 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
 *
 * Ohad Ben-Cohen <ohad@wizery.com>
 * Brian Swetland <swetland@google.com>
@@ -290,6 +291,42 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
}
EXPORT_SYMBOL(rpmsg_trysend_offchannel);

/**
 * rpmsg_get_sigs() - get the signals for this endpoint
 * @ept:	the rpmsg endpoint
 * @sigs:	serial signals bitmask
 *
 * Returns 0 on success and an appropriate error value on failure.
 */
int rpmsg_get_sigs(struct rpmsg_endpoint *ept, u32 *lsigs, u32 *rsigs)
{
	if (WARN_ON(!ept))
		return -EINVAL;
	if (!ept->ops->get_sigs)
		return -ENXIO;

	return ept->ops->get_sigs(ept, lsigs, rsigs);
}
EXPORT_SYMBOL(rpmsg_get_sigs);

/**
 * rpmsg_set_sigs() - set the remote signals for this endpoint
 * @ept:	the rpmsg endpoint
 * @sigs:	serial signals bitmask
 *
 * Returns 0 on success and an appropriate error value on failure.
 */
int rpmsg_set_sigs(struct rpmsg_endpoint *ept, u32 sigs)
{
	if (WARN_ON(!ept))
		return -EINVAL;
	if (!ept->ops->set_sigs)
		return -ENXIO;

	return ept->ops->set_sigs(ept, sigs);
}
EXPORT_SYMBOL(rpmsg_set_sigs);

/*
 * match an rpmsg channel with a channel info struct.
 * this is used to make sure we're not creating rpmsg devices for channels
@@ -432,6 +469,10 @@ static int rpmsg_dev_probe(struct device *dev)

		rpdev->ept = ept;
		rpdev->src = ept->addr;

		if (rpdrv->signals)
			ept->sig_cb = rpdrv->signals;

	}

	err = rpdrv->probe(rpdev);
+5 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
 *
 * Copyright (C) 2011 Texas Instruments, Inc.
 * Copyright (C) 2011 Google, Inc.
 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
 *
 * Ohad Ben-Cohen <ohad@wizery.com>
 * Brian Swetland <swetland@google.com>
@@ -54,6 +55,8 @@ struct rpmsg_device_ops {
 * @trysend:		see @rpmsg_trysend(), required
 * @trysendto:		see @rpmsg_trysendto(), optional
 * @trysend_offchannel:	see @rpmsg_trysend_offchannel(), optional
 * @get_sigs:		see @rpmsg_get_sigs(), optional
 * @set_sigs:		see @rpmsg_set_sigs(), optional
 *
 * Indirection table for the operations that a rpmsg backend should implement.
 * In addition to @destroy_ept, the backend must at least implement @send and
@@ -73,6 +76,8 @@ struct rpmsg_endpoint_ops {
			     void *data, int len);
	unsigned int (*poll)(struct rpmsg_endpoint *ept, struct file *filp,
			     poll_table *wait);
	int (*get_sigs)(struct rpmsg_endpoint *ept, u32 *lsigs, u32 *rsigs);
	int (*set_sigs)(struct rpmsg_endpoint *ept, u32 sigs);
};

int rpmsg_register_device(struct rpmsg_device *rpdev);
+126 −8
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/termios.h>

/* Define IPC Logging Macros */
#define GLINK_PKT_IPC_LOG_PAGE_CNT 2
@@ -52,6 +53,38 @@ do { \
	ipc_log_string(glink_pkt_ilctxt, "[%s]: "x, __func__, ##__VA_ARGS__); \
} while (0)

#define SMD_DTR_SIG BIT(31)
#define SMD_CTS_SIG BIT(30)
#define SMD_CD_SIG BIT(29)
#define SMD_RI_SIG BIT(28)

#define to_smd_signal(sigs) \
do { \
	sigs &= 0x0fff; \
	if (sigs & TIOCM_DTR) \
		sigs |= SMD_DTR_SIG; \
	if (sigs & TIOCM_RTS) \
		sigs |= SMD_CTS_SIG; \
	if (sigs & TIOCM_CD) \
		sigs |= SMD_CD_SIG; \
	if (sigs & TIOCM_RI) \
		sigs |= SMD_RI_SIG; \
} while (0)

#define from_smd_signal(sigs) \
do { \
	if (sigs & SMD_DTR_SIG) \
		sigs |= TIOCM_DSR; \
	if (sigs & SMD_CTS_SIG) \
		sigs |= TIOCM_CTS; \
	if (sigs & SMD_CD_SIG) \
		sigs |= TIOCM_CD; \
	if (sigs & SMD_RI_SIG) \
		sigs |= TIOCM_RI; \
	sigs &= 0x0fff; \
} while (0)


#define MODULE_NAME "glink_pkt"
static dev_t glink_pkt_major;
static struct class *glink_pkt_class;
@@ -71,6 +104,7 @@ static DEFINE_IDA(glink_pkt_minor_ida);
 * @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 glink_pkt device
 * @ch_name:	glink channel to match to
 * @edge:	glink edge to match to
@@ -89,6 +123,7 @@ struct glink_pkt_device {
	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;
@@ -156,6 +191,7 @@ static int glink_pkt_rpdev_cb(struct rpmsg_device *rpdev, void *buf, int len,
			      void *priv, u32 addr)
{
	struct glink_pkt_device *gpdev = priv;
	unsigned long flags;
	struct sk_buff *skb;

	skb = alloc_skb(len, GFP_ATOMIC);
@@ -164,9 +200,26 @@ static int glink_pkt_rpdev_cb(struct rpmsg_device *rpdev, void *buf, int len,

	skb_put_data(skb, buf, len);

	spin_lock(&gpdev->queue_lock);
	spin_lock_irqsave(&gpdev->queue_lock, flags);
	skb_queue_tail(&gpdev->queue, skb);
	spin_unlock(&gpdev->queue_lock);
	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, 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);
@@ -243,13 +296,14 @@ 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:%ld ref_cnt[%d]\n",
		       gpdev->ch_name, current->comm,
		       task_pid_nr(current), refcount_read(&gpdev->refcount));

	if (refcount_dec_and_test(&gpdev->refcount)) {
		spin_lock(&gpdev->queue_lock);
		spin_lock_irqsave(&gpdev->queue_lock, flags);

		/* Discard all SKBs */
		while (!skb_queue_empty(&gpdev->queue)) {
@@ -257,7 +311,8 @@ int glink_pkt_release(struct inode *inode, struct file *file)
			kfree_skb(skb);
		}
		wake_up_interruptible(&gpdev->readq);
		spin_unlock(&gpdev->queue_lock);
		gpdev->sig_change = false;
		spin_unlock_irqrestore(&gpdev->queue_lock, flags);
	}

	put_device(dev);
@@ -404,6 +459,7 @@ static unsigned int glink_pkt_poll(struct file *file, poll_table *wait)
{
	struct glink_pkt_device *gpdev = file->private_data;
	unsigned int mask = 0;
	unsigned long flags;

	gpdev = file->private_data;
	if (!gpdev || !refcount_read(&gpdev->refcount)) {
@@ -425,10 +481,13 @@ static unsigned int glink_pkt_poll(struct file *file, poll_table *wait)
		return POLLHUP;
	}

	spin_lock(&gpdev->queue_lock);
	spin_lock_irqsave(&gpdev->queue_lock, flags);
	if (!skb_queue_empty(&gpdev->queue))
		mask |= POLLIN | POLLRDNORM;
	spin_unlock(&gpdev->queue_lock);

	if (gpdev->sig_change)
		mask |= POLLPRI;
	spin_unlock_irqrestore(&gpdev->queue_lock, flags);

	mask |= rpmsg_poll(gpdev->rpdev->ept, file, wait);

@@ -437,6 +496,47 @@ static unsigned int glink_pkt_poll(struct file *file, poll_table *wait)
	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,
			      unsigned long arg)
{
	u32 lsigs, rsigs, val;
	int ret;

	ret = get_user(val, (u32 *)arg);
	if (ret)
		return ret;

	to_smd_signal(val);
	ret = rpmsg_get_sigs(gpdev->rpdev->ept, &lsigs, &rsigs);
	if (ret < 0) {
		GLINK_PKT_ERR("%s: Get signals failed[%d]\n", __func__, ret);
		return ret;
	}
	switch (cmd) {
	case TIOCMBIS:
		lsigs |= val;
		break;
	case TIOCMBIC:
		lsigs &= ~val;
		break;
	case TIOCMSET:
		lsigs = val;
		break;
	}
	ret = rpmsg_set_sigs(gpdev->rpdev->ept, lsigs);
	GLINK_PKT_INFO("sigs[0x%x] ret[%d]\n", lsigs, ret);
	return ret;
}

/**
 * glink_pkt_ioctl() - ioctl() syscall for the glink_pkt device
@@ -452,6 +552,8 @@ static long glink_pkt_ioctl(struct file *file, unsigned int cmd,
			    unsigned long arg)
{
	struct glink_pkt_device *gpdev;
	unsigned long flags;
	u32 lsigs, rsigs;
	int ret;

	gpdev = file->private_data;
@@ -469,9 +571,23 @@ static long glink_pkt_ioctl(struct file *file, unsigned int cmd,
	}

	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_sigs(gpdev->rpdev->ept, &lsigs, &rsigs);
		from_smd_signal(rsigs);
		if (!ret)
			ret = put_user(rsigs, (uint32_t *)arg);
		break;
	case TIOCMSET:
	case TIOCMBIS:
	case TIOCMBIC:
		ret = glink_pkt_tiocmset(gpdev, cmd, arg);
		break;
	/*
	 * Need to add support later for GLINK Signals and check with any client
	 * if they have any intent requirements.
	 * Need to add support later any intent requirements.
	 */
	default:
		GLINK_PKT_ERR("unrecognized ioctl command 0x%x\n", cmd);
@@ -578,6 +694,7 @@ static int glink_pkt_init_rpmsg(struct glink_pkt_device *gpdev)
	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;

@@ -616,6 +733,7 @@ static int glink_pkt_create_device(struct device *parent,

	/* Default open timeout for open is 120 sec */
	gpdev->open_tout = 120;
	gpdev->sig_change = false;

	spin_lock_init(&gpdev->queue_lock);
	skb_queue_head_init(&gpdev->queue);
+26 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
 * Copyright (C) 2011 Texas Instruments, Inc.
 * Copyright (C) 2011 Google, Inc.
 * All rights reserved.
 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
@@ -85,12 +86,14 @@ struct rpmsg_device {
};

typedef int (*rpmsg_rx_cb_t)(struct rpmsg_device *, void *, int, void *, u32);
typedef int (*rpmsg_rx_sig_t)(struct rpmsg_device *, u32, u32);

/**
 * struct rpmsg_endpoint - binds a local rpmsg address to its user
 * @rpdev: rpmsg channel device
 * @refcount: when this drops to zero, the ept is deallocated
 * @cb: rx callback handler
 * @sig_cb: rx serial signal handler
 * @cb_lock: must be taken before accessing/changing @cb
 * @addr: local rpmsg address
 * @priv: private data for the driver's use
@@ -113,6 +116,7 @@ struct rpmsg_endpoint {
	struct rpmsg_device *rpdev;
	struct kref refcount;
	rpmsg_rx_cb_t cb;
	rpmsg_rx_sig_t sig_cb;
	struct mutex cb_lock;
	u32 addr;
	void *priv;
@@ -127,6 +131,7 @@ struct rpmsg_endpoint {
 * @probe: invoked when a matching rpmsg channel (i.e. device) is found
 * @remove: invoked when the rpmsg channel is removed
 * @callback: invoked when an inbound message is received on the channel
 * @signals: invoked when a serial signal change is received on the channel
 */
struct rpmsg_driver {
	struct device_driver drv;
@@ -134,6 +139,7 @@ struct rpmsg_driver {
	int (*probe)(struct rpmsg_device *dev);
	void (*remove)(struct rpmsg_device *dev);
	int (*callback)(struct rpmsg_device *, void *, int, void *, u32);
	int (*signals)(struct rpmsg_device *, u32, u32);
};

#if IS_ENABLED(CONFIG_RPMSG)
@@ -160,6 +166,9 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
unsigned int rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
			poll_table *wait);

int rpmsg_get_sigs(struct rpmsg_endpoint *ept, u32 *lsigs, u32 *rsigs);
int rpmsg_set_sigs(struct rpmsg_endpoint *ept, u32 sigs);

#else

static inline int register_rpmsg_device(struct rpmsg_device *dev)
@@ -267,6 +276,23 @@ static inline unsigned int rpmsg_poll(struct rpmsg_endpoint *ept,
	return 0;
}

static inline int rpmsg_get_sigs(struct rpmsg_endpoint *ept, u32 *lsigs,
				 u32 *rsigs)
{
	/* This shouldn't be possible */
	WARN_ON(1);

	return -ENXIO;
}

static inline int rpmsg_set_sigs(struct rpmsg_endpoint *ept, u32 sigs)
{
	/* This shouldn't be possible */
	WARN_ON(1);

	return -ENXIO;
}

#endif /* IS_ENABLED(CONFIG_RPMSG) */

/* use a macro to avoid include chaining to get THIS_MODULE */