linux/drivers/rpmsg/qcom_smd.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2015, Sony Mobile Communications AB.
 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
 */

#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/sched.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smem.h>
#include <linux/wait.h>
#include <linux/rpmsg.h>
#include <linux/rpmsg/qcom_smd.h>

#include "rpmsg_internal.h"

/*
 * The Qualcomm Shared Memory communication solution provides point-to-point
 * channels for clients to send and receive streaming or packet based data.
 *
 * Each channel consists of a control item (channel info) and a ring buffer
 * pair. The channel info carry information related to channel state, flow
 * control and the offsets within the ring buffer.
 *
 * All allocated channels are listed in an allocation table, identifying the
 * pair of items by name, type and remote processor.
 *
 * Upon creating a new channel the remote processor allocates channel info and
 * ring buffer items from the smem heap and populate the allocation table. An
 * interrupt is sent to the other end of the channel and a scan for new
 * channels should be done. A channel never goes away, it will only change
 * state.
 *
 * The remote processor signals it intent for bring up the communication
 * channel by setting the state of its end of the channel to "opening" and
 * sends out an interrupt. We detect this change and register a smd device to
 * consume the channel. Upon finding a consumer we finish the handshake and the
 * channel is up.
 *
 * Upon closing a channel, the remote processor will update the state of its
 * end of the channel and signal us, we will then unregister any attached
 * device and close our end of the channel.
 *
 * Devices attached to a channel can use the qcom_smd_send function to push
 * data to the channel, this is done by copying the data into the tx ring
 * buffer, updating the pointers in the channel info and signaling the remote
 * processor.
 *
 * The remote processor does the equivalent when it transfer data and upon
 * receiving the interrupt we check the channel info for new data and delivers
 * this to the attached device. If the device is not ready to receive the data
 * we leave it in the ring buffer for now.
 */

struct smd_channel_info;
struct smd_channel_info_pair;
struct smd_channel_info_word;
struct smd_channel_info_word_pair;

static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops;

#define SMD_ALLOC_TBL_COUNT
#define SMD_ALLOC_TBL_SIZE

/*
 * This lists the various smem heap items relevant for the allocation table and
 * smd channel entries.
 */
static const struct {} smem_items[SMD_ALLOC_TBL_COUNT] =;

/**
 * struct qcom_smd_edge - representing a remote processor
 * @dev:		device associated with this edge
 * @name:		name of this edge
 * @of_node:		of_node handle for information related to this edge
 * @edge_id:		identifier of this edge
 * @remote_pid:		identifier of remote processor
 * @irq:		interrupt for signals on this edge
 * @ipc_regmap:		regmap handle holding the outgoing ipc register
 * @ipc_offset:		offset within @ipc_regmap of the register for ipc
 * @ipc_bit:		bit in the register at @ipc_offset of @ipc_regmap
 * @mbox_client:	mailbox client handle
 * @mbox_chan:		apcs ipc mailbox channel handle
 * @channels:		list of all channels detected on this edge
 * @channels_lock:	guard for modifications of @channels
 * @allocated:		array of bitmaps representing already allocated channels
 * @smem_available:	last available amount of smem triggering a channel scan
 * @new_channel_event:	wait queue for new channel events
 * @scan_work:		work item for discovering new channels
 * @state_work:		work item for edge state changes
 */
struct qcom_smd_edge {};

/*
 * SMD channel states.
 */
enum smd_channel_state {};

struct qcom_smd_device {};

struct qcom_smd_endpoint {};

#define to_smd_device(r)
#define to_smd_edge(d)
#define to_smd_endpoint(e)

/**
 * struct qcom_smd_channel - smd channel struct
 * @edge:		qcom_smd_edge this channel is living on
 * @qsept:		reference to a associated smd endpoint
 * @registered:		flag to indicate if the channel is registered
 * @name:		name of the channel
 * @state:		local state of the channel
 * @remote_state:	remote state of the channel
 * @state_change_event:	state change event
 * @info:		byte aligned outgoing/incoming channel info
 * @info_word:		word aligned outgoing/incoming channel info
 * @tx_lock:		lock to make writes to the channel mutually exclusive
 * @fblockread_event:	wakeup event tied to tx fBLOCKREADINTR
 * @tx_fifo:		pointer to the outgoing ring buffer
 * @rx_fifo:		pointer to the incoming ring buffer
 * @fifo_size:		size of each ring buffer
 * @bounce_buffer:	bounce buffer for reading wrapped packets
 * @cb:			callback function registered for this channel
 * @recv_lock:		guard for rx info modifications and cb pointer
 * @pkt_size:		size of the currently handled packet
 * @drvdata:		driver private data
 * @list:		lite entry for @channels in qcom_smd_edge
 */
struct qcom_smd_channel {};

/*
 * Format of the smd_info smem items, for byte aligned channels.
 */
struct smd_channel_info {};

struct smd_channel_info_pair {};

/*
 * Format of the smd_info smem items, for word aligned channels.
 */
struct smd_channel_info_word {};

struct smd_channel_info_word_pair {};

#define GET_RX_CHANNEL_FLAG(channel, param)

#define GET_RX_CHANNEL_INFO(channel, param)

#define SET_RX_CHANNEL_FLAG(channel, param, value)

#define SET_RX_CHANNEL_INFO(channel, param, value)

#define GET_TX_CHANNEL_FLAG(channel, param)

#define GET_TX_CHANNEL_INFO(channel, param)

#define SET_TX_CHANNEL_FLAG(channel, param, value)

#define SET_TX_CHANNEL_INFO(channel, param, value)

/**
 * struct qcom_smd_alloc_entry - channel allocation entry
 * @name:	channel name
 * @cid:	channel index
 * @flags:	channel flags and edge id
 * @ref_count:	reference count of the channel
 */
struct qcom_smd_alloc_entry {} __packed;

#define SMD_CHANNEL_FLAGS_EDGE_MASK
#define SMD_CHANNEL_FLAGS_STREAM
#define SMD_CHANNEL_FLAGS_PACKET

/*
 * Each smd packet contains a 20 byte header, with the first 4 being the length
 * of the packet.
 */
#define SMD_PACKET_HEADER_LEN

/*
 * Signal the remote processor associated with 'channel'.
 */
static void qcom_smd_signal_channel(struct qcom_smd_channel *channel)
{}

/*
 * Initialize the tx channel info
 */
static void qcom_smd_channel_reset(struct qcom_smd_channel *channel)
{}

/*
 * Set the callback for a channel, with appropriate locking
 */
static void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel,
					  rpmsg_rx_cb_t cb)
{
	struct rpmsg_endpoint *ept = &channel->qsept->ept;
	unsigned long flags;

	spin_lock_irqsave(&channel->recv_lock, flags);
	ept->cb = cb;
	spin_unlock_irqrestore(&channel->recv_lock, flags);
};

/*
 * Calculate the amount of data available in the rx fifo
 */
static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel)
{}

/*
 * Set tx channel state and inform the remote processor
 */
static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel,
				       int state)
{}

/*
 * Copy count bytes of data using 32bit accesses, if that's required.
 */
static void smd_copy_to_fifo(void __iomem *dst,
			     const void *src,
			     size_t count,
			     bool word_aligned)
{}

/*
 * Copy count bytes of data using 32bit accesses, if that is required.
 */
static void smd_copy_from_fifo(void *dst,
			       const void __iomem *src,
			       size_t count,
			       bool word_aligned)
{}

/*
 * Read count bytes of data from the rx fifo into buf, but don't advance the
 * tail.
 */
static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel,
				    void *buf, size_t count)
{}

/*
 * Advance the rx tail by count bytes.
 */
static void qcom_smd_channel_advance(struct qcom_smd_channel *channel,
				     size_t count)
{}

/*
 * Read out a single packet from the rx fifo and deliver it to the device
 */
static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel)
{}

/*
 * Per channel interrupt handling
 */
static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel)
{}

/*
 * The edge interrupts are triggered by the remote processor on state changes,
 * channel info updates or when new channels are created.
 */
static irqreturn_t qcom_smd_edge_intr(int irq, void *data)
{}

/*
 * Calculate how much space is available in the tx fifo.
 */
static size_t qcom_smd_get_tx_avail(struct qcom_smd_channel *channel)
{}

/*
 * Write count bytes of data into channel, possibly wrapping in the ring buffer
 */
static int qcom_smd_write_fifo(struct qcom_smd_channel *channel,
			       const void *data,
			       size_t count)
{}

/**
 * __qcom_smd_send - write data to smd channel
 * @channel:	channel handle
 * @data:	buffer of data to write
 * @len:	number of bytes to write
 * @wait:	flag to indicate if write can wait
 *
 * This is a blocking write of len bytes into the channel's tx ring buffer and
 * signal the remote end. It will sleep until there is enough space available
 * in the tx buffer, utilizing the fBLOCKREADINTR signaling mechanism to avoid
 * polling.
 */
static int __qcom_smd_send(struct qcom_smd_channel *channel, const void *data,
			   int len, bool wait)
{}

/*
 * Helper for opening a channel
 */
static int qcom_smd_channel_open(struct qcom_smd_channel *channel,
				 rpmsg_rx_cb_t cb)
{}

/*
 * Helper for closing and resetting a channel
 */
static void qcom_smd_channel_close(struct qcom_smd_channel *channel)
{}

static struct qcom_smd_channel *
qcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name)
{}

static void __ept_release(struct kref *kref)
{}

static struct rpmsg_endpoint *qcom_smd_create_ept(struct rpmsg_device *rpdev,
						  rpmsg_rx_cb_t cb, void *priv,
						  struct rpmsg_channel_info chinfo)
{}

static void qcom_smd_destroy_ept(struct rpmsg_endpoint *ept)
{}

static int qcom_smd_send(struct rpmsg_endpoint *ept, void *data, int len)
{}

static int qcom_smd_trysend(struct rpmsg_endpoint *ept, void *data, int len)
{}

static int qcom_smd_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)
{}

static int qcom_smd_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)
{}

static __poll_t qcom_smd_poll(struct rpmsg_endpoint *ept,
				  struct file *filp, poll_table *wait)
{}

/*
 * Finds the device_node for the smd child interested in this channel.
 */
static struct device_node *qcom_smd_match_channel(struct device_node *edge_node,
						  const char *channel)
{}

static int qcom_smd_announce_create(struct rpmsg_device *rpdev)
{}

static const struct rpmsg_device_ops qcom_smd_device_ops =;

static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops =;

static void qcom_smd_release_device(struct device *dev)
{}

/*
 * Create a smd client device for channel that is being opened.
 */
static int qcom_smd_create_device(struct qcom_smd_channel *channel)
{}

static int qcom_smd_create_chrdev(struct qcom_smd_edge *edge)
{}

/*
 * Allocate the qcom_smd_channel object for a newly found smd channel,
 * retrieving and validating the smem items involved.
 */
static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *edge,
							unsigned smem_info_item,
							unsigned smem_fifo_item,
							char *name)
{}

/*
 * Scans the allocation table for any newly allocated channels, calls
 * qcom_smd_create_channel() to create representations of these and add
 * them to the edge's list of channels.
 */
static void qcom_channel_scan_worker(struct work_struct *work)
{}

/*
 * This per edge worker scans smem for any new channels and register these. It
 * then scans all registered channels for state changes that should be handled
 * by creating or destroying smd client devices for the registered channels.
 *
 * LOCKING: edge->channels_lock only needs to cover the list operations, as the
 * worker is killed before any channels are deallocated
 */
static void qcom_channel_state_worker(struct work_struct *work)
{}

/*
 * Parses an of_node describing an edge.
 */
static int qcom_smd_parse_edge(struct device *dev,
			       struct device_node *node,
			       struct qcom_smd_edge *edge)
{}

/*
 * Release function for an edge.
  * Reset the state of each associated channel and free the edge context.
 */
static void qcom_smd_edge_release(struct device *dev)
{}

static ssize_t rpmsg_name_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{}
static DEVICE_ATTR_RO(rpmsg_name);

static struct attribute *qcom_smd_edge_attrs[] =;
ATTRIBUTE_GROUPS();

/**
 * qcom_smd_register_edge() - register an edge based on an device_node
 * @parent:    parent device for the edge
 * @node:      device_node describing the edge
 *
 * Return: an edge reference, or negative ERR_PTR() on failure.
 */
struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent,
					     struct device_node *node)
{}
EXPORT_SYMBOL();

static int qcom_smd_remove_device(struct device *dev, void *data)
{}

/**
 * qcom_smd_unregister_edge() - release an edge and its children
 * @edge:      edge reference acquired from qcom_smd_register_edge
 */
void qcom_smd_unregister_edge(struct qcom_smd_edge *edge)
{}
EXPORT_SYMBOL();

static int qcom_smd_probe(struct platform_device *pdev)
{}

static int qcom_smd_remove_edge(struct device *dev, void *data)
{}

/*
 * Shut down all smd clients by making sure that each edge stops processing
 * events and scanning for new channels, then call destroy on the devices.
 */
static void qcom_smd_remove(struct platform_device *pdev)
{}

static const struct of_device_id qcom_smd_of_match[] =;
MODULE_DEVICE_TABLE(of, qcom_smd_of_match);

static struct platform_driver qcom_smd_driver =;

static int __init qcom_smd_init(void)
{}
arch_initcall(qcom_smd_init);

static void __exit qcom_smd_exit(void)
{}
module_exit(qcom_smd_exit);

MODULE_AUTHOR();
MODULE_DESCRIPTION();
MODULE_LICENSE();