153e2822eSBjorn Andersson /* 253e2822eSBjorn Andersson * Copyright (c) 2015, Sony Mobile Communications AB. 353e2822eSBjorn Andersson * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 453e2822eSBjorn Andersson * 553e2822eSBjorn Andersson * This program is free software; you can redistribute it and/or modify 653e2822eSBjorn Andersson * it under the terms of the GNU General Public License version 2 and 753e2822eSBjorn Andersson * only version 2 as published by the Free Software Foundation. 853e2822eSBjorn Andersson * 953e2822eSBjorn Andersson * This program is distributed in the hope that it will be useful, 1053e2822eSBjorn Andersson * but WITHOUT ANY WARRANTY; without even the implied warranty of 1153e2822eSBjorn Andersson * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1253e2822eSBjorn Andersson * GNU General Public License for more details. 1353e2822eSBjorn Andersson */ 1453e2822eSBjorn Andersson 1553e2822eSBjorn Andersson #include <linux/interrupt.h> 1653e2822eSBjorn Andersson #include <linux/io.h> 1753e2822eSBjorn Andersson #include <linux/mfd/syscon.h> 1853e2822eSBjorn Andersson #include <linux/module.h> 1953e2822eSBjorn Andersson #include <linux/of_irq.h> 2053e2822eSBjorn Andersson #include <linux/of_platform.h> 2153e2822eSBjorn Andersson #include <linux/platform_device.h> 2253e2822eSBjorn Andersson #include <linux/regmap.h> 2353e2822eSBjorn Andersson #include <linux/sched.h> 2453e2822eSBjorn Andersson #include <linux/slab.h> 2553e2822eSBjorn Andersson #include <linux/soc/qcom/smem.h> 2653e2822eSBjorn Andersson #include <linux/wait.h> 2753e2822eSBjorn Andersson #include <linux/rpmsg.h> 28*8fc94723SBjorn Andersson #include <linux/rpmsg/qcom_smd.h> 2953e2822eSBjorn Andersson 3053e2822eSBjorn Andersson #include "rpmsg_internal.h" 3153e2822eSBjorn Andersson 3253e2822eSBjorn Andersson /* 3353e2822eSBjorn Andersson * The Qualcomm Shared Memory communication solution provides point-to-point 3453e2822eSBjorn Andersson * channels for clients to send and receive streaming or packet based data. 3553e2822eSBjorn Andersson * 3653e2822eSBjorn Andersson * Each channel consists of a control item (channel info) and a ring buffer 3753e2822eSBjorn Andersson * pair. The channel info carry information related to channel state, flow 3853e2822eSBjorn Andersson * control and the offsets within the ring buffer. 3953e2822eSBjorn Andersson * 4053e2822eSBjorn Andersson * All allocated channels are listed in an allocation table, identifying the 4153e2822eSBjorn Andersson * pair of items by name, type and remote processor. 4253e2822eSBjorn Andersson * 4353e2822eSBjorn Andersson * Upon creating a new channel the remote processor allocates channel info and 4453e2822eSBjorn Andersson * ring buffer items from the smem heap and populate the allocation table. An 4553e2822eSBjorn Andersson * interrupt is sent to the other end of the channel and a scan for new 4653e2822eSBjorn Andersson * channels should be done. A channel never goes away, it will only change 4753e2822eSBjorn Andersson * state. 4853e2822eSBjorn Andersson * 4953e2822eSBjorn Andersson * The remote processor signals it intent for bring up the communication 5053e2822eSBjorn Andersson * channel by setting the state of its end of the channel to "opening" and 5153e2822eSBjorn Andersson * sends out an interrupt. We detect this change and register a smd device to 5253e2822eSBjorn Andersson * consume the channel. Upon finding a consumer we finish the handshake and the 5353e2822eSBjorn Andersson * channel is up. 5453e2822eSBjorn Andersson * 5553e2822eSBjorn Andersson * Upon closing a channel, the remote processor will update the state of its 5653e2822eSBjorn Andersson * end of the channel and signal us, we will then unregister any attached 5753e2822eSBjorn Andersson * device and close our end of the channel. 5853e2822eSBjorn Andersson * 5953e2822eSBjorn Andersson * Devices attached to a channel can use the qcom_smd_send function to push 6053e2822eSBjorn Andersson * data to the channel, this is done by copying the data into the tx ring 6153e2822eSBjorn Andersson * buffer, updating the pointers in the channel info and signaling the remote 6253e2822eSBjorn Andersson * processor. 6353e2822eSBjorn Andersson * 6453e2822eSBjorn Andersson * The remote processor does the equivalent when it transfer data and upon 6553e2822eSBjorn Andersson * receiving the interrupt we check the channel info for new data and delivers 6653e2822eSBjorn Andersson * this to the attached device. If the device is not ready to receive the data 6753e2822eSBjorn Andersson * we leave it in the ring buffer for now. 6853e2822eSBjorn Andersson */ 6953e2822eSBjorn Andersson 7053e2822eSBjorn Andersson struct smd_channel_info; 7153e2822eSBjorn Andersson struct smd_channel_info_pair; 7253e2822eSBjorn Andersson struct smd_channel_info_word; 7353e2822eSBjorn Andersson struct smd_channel_info_word_pair; 7453e2822eSBjorn Andersson 7553e2822eSBjorn Andersson static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops; 7653e2822eSBjorn Andersson 7753e2822eSBjorn Andersson #define SMD_ALLOC_TBL_COUNT 2 7853e2822eSBjorn Andersson #define SMD_ALLOC_TBL_SIZE 64 7953e2822eSBjorn Andersson 8053e2822eSBjorn Andersson /* 8153e2822eSBjorn Andersson * This lists the various smem heap items relevant for the allocation table and 8253e2822eSBjorn Andersson * smd channel entries. 8353e2822eSBjorn Andersson */ 8453e2822eSBjorn Andersson static const struct { 8553e2822eSBjorn Andersson unsigned alloc_tbl_id; 8653e2822eSBjorn Andersson unsigned info_base_id; 8753e2822eSBjorn Andersson unsigned fifo_base_id; 8853e2822eSBjorn Andersson } smem_items[SMD_ALLOC_TBL_COUNT] = { 8953e2822eSBjorn Andersson { 9053e2822eSBjorn Andersson .alloc_tbl_id = 13, 9153e2822eSBjorn Andersson .info_base_id = 14, 9253e2822eSBjorn Andersson .fifo_base_id = 338 9353e2822eSBjorn Andersson }, 9453e2822eSBjorn Andersson { 9553e2822eSBjorn Andersson .alloc_tbl_id = 266, 9653e2822eSBjorn Andersson .info_base_id = 138, 9753e2822eSBjorn Andersson .fifo_base_id = 202, 9853e2822eSBjorn Andersson }, 9953e2822eSBjorn Andersson }; 10053e2822eSBjorn Andersson 10153e2822eSBjorn Andersson /** 10253e2822eSBjorn Andersson * struct qcom_smd_edge - representing a remote processor 10353e2822eSBjorn Andersson * @of_node: of_node handle for information related to this edge 10453e2822eSBjorn Andersson * @edge_id: identifier of this edge 10553e2822eSBjorn Andersson * @remote_pid: identifier of remote processor 10653e2822eSBjorn Andersson * @irq: interrupt for signals on this edge 10753e2822eSBjorn Andersson * @ipc_regmap: regmap handle holding the outgoing ipc register 10853e2822eSBjorn Andersson * @ipc_offset: offset within @ipc_regmap of the register for ipc 10953e2822eSBjorn Andersson * @ipc_bit: bit in the register at @ipc_offset of @ipc_regmap 11053e2822eSBjorn Andersson * @channels: list of all channels detected on this edge 11153e2822eSBjorn Andersson * @channels_lock: guard for modifications of @channels 11253e2822eSBjorn Andersson * @allocated: array of bitmaps representing already allocated channels 11353e2822eSBjorn Andersson * @smem_available: last available amount of smem triggering a channel scan 11453e2822eSBjorn Andersson * @scan_work: work item for discovering new channels 11553e2822eSBjorn Andersson * @state_work: work item for edge state changes 11653e2822eSBjorn Andersson */ 11753e2822eSBjorn Andersson struct qcom_smd_edge { 11853e2822eSBjorn Andersson struct device dev; 11953e2822eSBjorn Andersson 12053e2822eSBjorn Andersson struct device_node *of_node; 12153e2822eSBjorn Andersson unsigned edge_id; 12253e2822eSBjorn Andersson unsigned remote_pid; 12353e2822eSBjorn Andersson 12453e2822eSBjorn Andersson int irq; 12553e2822eSBjorn Andersson 12653e2822eSBjorn Andersson struct regmap *ipc_regmap; 12753e2822eSBjorn Andersson int ipc_offset; 12853e2822eSBjorn Andersson int ipc_bit; 12953e2822eSBjorn Andersson 13053e2822eSBjorn Andersson struct list_head channels; 13153e2822eSBjorn Andersson spinlock_t channels_lock; 13253e2822eSBjorn Andersson 13353e2822eSBjorn Andersson DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE); 13453e2822eSBjorn Andersson 13553e2822eSBjorn Andersson unsigned smem_available; 13653e2822eSBjorn Andersson 13753e2822eSBjorn Andersson wait_queue_head_t new_channel_event; 13853e2822eSBjorn Andersson 13953e2822eSBjorn Andersson struct work_struct scan_work; 14053e2822eSBjorn Andersson struct work_struct state_work; 14153e2822eSBjorn Andersson }; 14253e2822eSBjorn Andersson 14353e2822eSBjorn Andersson /* 14453e2822eSBjorn Andersson * SMD channel states. 14553e2822eSBjorn Andersson */ 14653e2822eSBjorn Andersson enum smd_channel_state { 14753e2822eSBjorn Andersson SMD_CHANNEL_CLOSED, 14853e2822eSBjorn Andersson SMD_CHANNEL_OPENING, 14953e2822eSBjorn Andersson SMD_CHANNEL_OPENED, 15053e2822eSBjorn Andersson SMD_CHANNEL_FLUSHING, 15153e2822eSBjorn Andersson SMD_CHANNEL_CLOSING, 15253e2822eSBjorn Andersson SMD_CHANNEL_RESET, 15353e2822eSBjorn Andersson SMD_CHANNEL_RESET_OPENING 15453e2822eSBjorn Andersson }; 15553e2822eSBjorn Andersson 15653e2822eSBjorn Andersson struct qcom_smd_device { 15753e2822eSBjorn Andersson struct rpmsg_device rpdev; 15853e2822eSBjorn Andersson 15953e2822eSBjorn Andersson struct qcom_smd_edge *edge; 16053e2822eSBjorn Andersson }; 16153e2822eSBjorn Andersson 16253e2822eSBjorn Andersson struct qcom_smd_endpoint { 16353e2822eSBjorn Andersson struct rpmsg_endpoint ept; 16453e2822eSBjorn Andersson 16553e2822eSBjorn Andersson struct qcom_smd_channel *qsch; 16653e2822eSBjorn Andersson }; 16753e2822eSBjorn Andersson 16853e2822eSBjorn Andersson #define to_smd_device(_rpdev) container_of(_rpdev, struct qcom_smd_device, rpdev) 16953e2822eSBjorn Andersson #define to_smd_edge(d) container_of(d, struct qcom_smd_edge, dev) 17053e2822eSBjorn Andersson #define to_smd_endpoint(ept) container_of(ept, struct qcom_smd_endpoint, ept) 17153e2822eSBjorn Andersson 17253e2822eSBjorn Andersson /** 17353e2822eSBjorn Andersson * struct qcom_smd_channel - smd channel struct 17453e2822eSBjorn Andersson * @edge: qcom_smd_edge this channel is living on 17553e2822eSBjorn Andersson * @qsdev: reference to a associated smd client device 17653e2822eSBjorn Andersson * @name: name of the channel 17753e2822eSBjorn Andersson * @state: local state of the channel 17853e2822eSBjorn Andersson * @remote_state: remote state of the channel 17953e2822eSBjorn Andersson * @info: byte aligned outgoing/incoming channel info 18053e2822eSBjorn Andersson * @info_word: word aligned outgoing/incoming channel info 18153e2822eSBjorn Andersson * @tx_lock: lock to make writes to the channel mutually exclusive 18253e2822eSBjorn Andersson * @fblockread_event: wakeup event tied to tx fBLOCKREADINTR 18353e2822eSBjorn Andersson * @tx_fifo: pointer to the outgoing ring buffer 18453e2822eSBjorn Andersson * @rx_fifo: pointer to the incoming ring buffer 18553e2822eSBjorn Andersson * @fifo_size: size of each ring buffer 18653e2822eSBjorn Andersson * @bounce_buffer: bounce buffer for reading wrapped packets 18753e2822eSBjorn Andersson * @cb: callback function registered for this channel 18853e2822eSBjorn Andersson * @recv_lock: guard for rx info modifications and cb pointer 18953e2822eSBjorn Andersson * @pkt_size: size of the currently handled packet 19053e2822eSBjorn Andersson * @list: lite entry for @channels in qcom_smd_edge 19153e2822eSBjorn Andersson */ 19253e2822eSBjorn Andersson struct qcom_smd_channel { 19353e2822eSBjorn Andersson struct qcom_smd_edge *edge; 19453e2822eSBjorn Andersson 19553e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept; 19653e2822eSBjorn Andersson bool registered; 19753e2822eSBjorn Andersson 19853e2822eSBjorn Andersson char *name; 19953e2822eSBjorn Andersson enum smd_channel_state state; 20053e2822eSBjorn Andersson enum smd_channel_state remote_state; 20153e2822eSBjorn Andersson 20253e2822eSBjorn Andersson struct smd_channel_info_pair *info; 20353e2822eSBjorn Andersson struct smd_channel_info_word_pair *info_word; 20453e2822eSBjorn Andersson 20553e2822eSBjorn Andersson struct mutex tx_lock; 20653e2822eSBjorn Andersson wait_queue_head_t fblockread_event; 20753e2822eSBjorn Andersson 20853e2822eSBjorn Andersson void *tx_fifo; 20953e2822eSBjorn Andersson void *rx_fifo; 21053e2822eSBjorn Andersson int fifo_size; 21153e2822eSBjorn Andersson 21253e2822eSBjorn Andersson void *bounce_buffer; 21353e2822eSBjorn Andersson 21453e2822eSBjorn Andersson spinlock_t recv_lock; 21553e2822eSBjorn Andersson 21653e2822eSBjorn Andersson int pkt_size; 21753e2822eSBjorn Andersson 21853e2822eSBjorn Andersson void *drvdata; 21953e2822eSBjorn Andersson 22053e2822eSBjorn Andersson struct list_head list; 22153e2822eSBjorn Andersson }; 22253e2822eSBjorn Andersson 22353e2822eSBjorn Andersson /* 22453e2822eSBjorn Andersson * Format of the smd_info smem items, for byte aligned channels. 22553e2822eSBjorn Andersson */ 22653e2822eSBjorn Andersson struct smd_channel_info { 22753e2822eSBjorn Andersson __le32 state; 22853e2822eSBjorn Andersson u8 fDSR; 22953e2822eSBjorn Andersson u8 fCTS; 23053e2822eSBjorn Andersson u8 fCD; 23153e2822eSBjorn Andersson u8 fRI; 23253e2822eSBjorn Andersson u8 fHEAD; 23353e2822eSBjorn Andersson u8 fTAIL; 23453e2822eSBjorn Andersson u8 fSTATE; 23553e2822eSBjorn Andersson u8 fBLOCKREADINTR; 23653e2822eSBjorn Andersson __le32 tail; 23753e2822eSBjorn Andersson __le32 head; 23853e2822eSBjorn Andersson }; 23953e2822eSBjorn Andersson 24053e2822eSBjorn Andersson struct smd_channel_info_pair { 24153e2822eSBjorn Andersson struct smd_channel_info tx; 24253e2822eSBjorn Andersson struct smd_channel_info rx; 24353e2822eSBjorn Andersson }; 24453e2822eSBjorn Andersson 24553e2822eSBjorn Andersson /* 24653e2822eSBjorn Andersson * Format of the smd_info smem items, for word aligned channels. 24753e2822eSBjorn Andersson */ 24853e2822eSBjorn Andersson struct smd_channel_info_word { 24953e2822eSBjorn Andersson __le32 state; 25053e2822eSBjorn Andersson __le32 fDSR; 25153e2822eSBjorn Andersson __le32 fCTS; 25253e2822eSBjorn Andersson __le32 fCD; 25353e2822eSBjorn Andersson __le32 fRI; 25453e2822eSBjorn Andersson __le32 fHEAD; 25553e2822eSBjorn Andersson __le32 fTAIL; 25653e2822eSBjorn Andersson __le32 fSTATE; 25753e2822eSBjorn Andersson __le32 fBLOCKREADINTR; 25853e2822eSBjorn Andersson __le32 tail; 25953e2822eSBjorn Andersson __le32 head; 26053e2822eSBjorn Andersson }; 26153e2822eSBjorn Andersson 26253e2822eSBjorn Andersson struct smd_channel_info_word_pair { 26353e2822eSBjorn Andersson struct smd_channel_info_word tx; 26453e2822eSBjorn Andersson struct smd_channel_info_word rx; 26553e2822eSBjorn Andersson }; 26653e2822eSBjorn Andersson 26753e2822eSBjorn Andersson #define GET_RX_CHANNEL_FLAG(channel, param) \ 26853e2822eSBjorn Andersson ({ \ 26953e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ 27053e2822eSBjorn Andersson channel->info_word ? \ 27153e2822eSBjorn Andersson le32_to_cpu(channel->info_word->rx.param) : \ 27253e2822eSBjorn Andersson channel->info->rx.param; \ 27353e2822eSBjorn Andersson }) 27453e2822eSBjorn Andersson 27553e2822eSBjorn Andersson #define GET_RX_CHANNEL_INFO(channel, param) \ 27653e2822eSBjorn Andersson ({ \ 27753e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \ 27853e2822eSBjorn Andersson le32_to_cpu(channel->info_word ? \ 27953e2822eSBjorn Andersson channel->info_word->rx.param : \ 28053e2822eSBjorn Andersson channel->info->rx.param); \ 28153e2822eSBjorn Andersson }) 28253e2822eSBjorn Andersson 28353e2822eSBjorn Andersson #define SET_RX_CHANNEL_FLAG(channel, param, value) \ 28453e2822eSBjorn Andersson ({ \ 28553e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ 28653e2822eSBjorn Andersson if (channel->info_word) \ 28753e2822eSBjorn Andersson channel->info_word->rx.param = cpu_to_le32(value); \ 28853e2822eSBjorn Andersson else \ 28953e2822eSBjorn Andersson channel->info->rx.param = value; \ 29053e2822eSBjorn Andersson }) 29153e2822eSBjorn Andersson 29253e2822eSBjorn Andersson #define SET_RX_CHANNEL_INFO(channel, param, value) \ 29353e2822eSBjorn Andersson ({ \ 29453e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \ 29553e2822eSBjorn Andersson if (channel->info_word) \ 29653e2822eSBjorn Andersson channel->info_word->rx.param = cpu_to_le32(value); \ 29753e2822eSBjorn Andersson else \ 29853e2822eSBjorn Andersson channel->info->rx.param = cpu_to_le32(value); \ 29953e2822eSBjorn Andersson }) 30053e2822eSBjorn Andersson 30153e2822eSBjorn Andersson #define GET_TX_CHANNEL_FLAG(channel, param) \ 30253e2822eSBjorn Andersson ({ \ 30353e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \ 30453e2822eSBjorn Andersson channel->info_word ? \ 30553e2822eSBjorn Andersson le32_to_cpu(channel->info_word->tx.param) : \ 30653e2822eSBjorn Andersson channel->info->tx.param; \ 30753e2822eSBjorn Andersson }) 30853e2822eSBjorn Andersson 30953e2822eSBjorn Andersson #define GET_TX_CHANNEL_INFO(channel, param) \ 31053e2822eSBjorn Andersson ({ \ 31153e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \ 31253e2822eSBjorn Andersson le32_to_cpu(channel->info_word ? \ 31353e2822eSBjorn Andersson channel->info_word->tx.param : \ 31453e2822eSBjorn Andersson channel->info->tx.param); \ 31553e2822eSBjorn Andersson }) 31653e2822eSBjorn Andersson 31753e2822eSBjorn Andersson #define SET_TX_CHANNEL_FLAG(channel, param, value) \ 31853e2822eSBjorn Andersson ({ \ 31953e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \ 32053e2822eSBjorn Andersson if (channel->info_word) \ 32153e2822eSBjorn Andersson channel->info_word->tx.param = cpu_to_le32(value); \ 32253e2822eSBjorn Andersson else \ 32353e2822eSBjorn Andersson channel->info->tx.param = value; \ 32453e2822eSBjorn Andersson }) 32553e2822eSBjorn Andersson 32653e2822eSBjorn Andersson #define SET_TX_CHANNEL_INFO(channel, param, value) \ 32753e2822eSBjorn Andersson ({ \ 32853e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \ 32953e2822eSBjorn Andersson if (channel->info_word) \ 33053e2822eSBjorn Andersson channel->info_word->tx.param = cpu_to_le32(value); \ 33153e2822eSBjorn Andersson else \ 33253e2822eSBjorn Andersson channel->info->tx.param = cpu_to_le32(value); \ 33353e2822eSBjorn Andersson }) 33453e2822eSBjorn Andersson 33553e2822eSBjorn Andersson /** 33653e2822eSBjorn Andersson * struct qcom_smd_alloc_entry - channel allocation entry 33753e2822eSBjorn Andersson * @name: channel name 33853e2822eSBjorn Andersson * @cid: channel index 33953e2822eSBjorn Andersson * @flags: channel flags and edge id 34053e2822eSBjorn Andersson * @ref_count: reference count of the channel 34153e2822eSBjorn Andersson */ 34253e2822eSBjorn Andersson struct qcom_smd_alloc_entry { 34353e2822eSBjorn Andersson u8 name[20]; 34453e2822eSBjorn Andersson __le32 cid; 34553e2822eSBjorn Andersson __le32 flags; 34653e2822eSBjorn Andersson __le32 ref_count; 34753e2822eSBjorn Andersson } __packed; 34853e2822eSBjorn Andersson 34953e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_EDGE_MASK 0xff 35053e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_STREAM BIT(8) 35153e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_PACKET BIT(9) 35253e2822eSBjorn Andersson 35353e2822eSBjorn Andersson /* 35453e2822eSBjorn Andersson * Each smd packet contains a 20 byte header, with the first 4 being the length 35553e2822eSBjorn Andersson * of the packet. 35653e2822eSBjorn Andersson */ 35753e2822eSBjorn Andersson #define SMD_PACKET_HEADER_LEN 20 35853e2822eSBjorn Andersson 35953e2822eSBjorn Andersson /* 36053e2822eSBjorn Andersson * Signal the remote processor associated with 'channel'. 36153e2822eSBjorn Andersson */ 36253e2822eSBjorn Andersson static void qcom_smd_signal_channel(struct qcom_smd_channel *channel) 36353e2822eSBjorn Andersson { 36453e2822eSBjorn Andersson struct qcom_smd_edge *edge = channel->edge; 36553e2822eSBjorn Andersson 36653e2822eSBjorn Andersson regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit)); 36753e2822eSBjorn Andersson } 36853e2822eSBjorn Andersson 36953e2822eSBjorn Andersson /* 37053e2822eSBjorn Andersson * Initialize the tx channel info 37153e2822eSBjorn Andersson */ 37253e2822eSBjorn Andersson static void qcom_smd_channel_reset(struct qcom_smd_channel *channel) 37353e2822eSBjorn Andersson { 37453e2822eSBjorn Andersson SET_TX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED); 37553e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fDSR, 0); 37653e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fCTS, 0); 37753e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fCD, 0); 37853e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fRI, 0); 37953e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fHEAD, 0); 38053e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fTAIL, 0); 38153e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); 38253e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1); 38353e2822eSBjorn Andersson SET_TX_CHANNEL_INFO(channel, head, 0); 38453e2822eSBjorn Andersson SET_RX_CHANNEL_INFO(channel, tail, 0); 38553e2822eSBjorn Andersson 38653e2822eSBjorn Andersson qcom_smd_signal_channel(channel); 38753e2822eSBjorn Andersson 38853e2822eSBjorn Andersson channel->state = SMD_CHANNEL_CLOSED; 38953e2822eSBjorn Andersson channel->pkt_size = 0; 39053e2822eSBjorn Andersson } 39153e2822eSBjorn Andersson 39253e2822eSBjorn Andersson /* 39353e2822eSBjorn Andersson * Set the callback for a channel, with appropriate locking 39453e2822eSBjorn Andersson */ 39553e2822eSBjorn Andersson static void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel, 39653e2822eSBjorn Andersson rpmsg_rx_cb_t cb) 39753e2822eSBjorn Andersson { 39853e2822eSBjorn Andersson struct rpmsg_endpoint *ept = &channel->qsept->ept; 39953e2822eSBjorn Andersson unsigned long flags; 40053e2822eSBjorn Andersson 40153e2822eSBjorn Andersson spin_lock_irqsave(&channel->recv_lock, flags); 40253e2822eSBjorn Andersson ept->cb = cb; 40353e2822eSBjorn Andersson spin_unlock_irqrestore(&channel->recv_lock, flags); 40453e2822eSBjorn Andersson }; 40553e2822eSBjorn Andersson 40653e2822eSBjorn Andersson /* 40753e2822eSBjorn Andersson * Calculate the amount of data available in the rx fifo 40853e2822eSBjorn Andersson */ 40953e2822eSBjorn Andersson static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel) 41053e2822eSBjorn Andersson { 41153e2822eSBjorn Andersson unsigned head; 41253e2822eSBjorn Andersson unsigned tail; 41353e2822eSBjorn Andersson 41453e2822eSBjorn Andersson head = GET_RX_CHANNEL_INFO(channel, head); 41553e2822eSBjorn Andersson tail = GET_RX_CHANNEL_INFO(channel, tail); 41653e2822eSBjorn Andersson 41753e2822eSBjorn Andersson return (head - tail) & (channel->fifo_size - 1); 41853e2822eSBjorn Andersson } 41953e2822eSBjorn Andersson 42053e2822eSBjorn Andersson /* 42153e2822eSBjorn Andersson * Set tx channel state and inform the remote processor 42253e2822eSBjorn Andersson */ 42353e2822eSBjorn Andersson static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel, 42453e2822eSBjorn Andersson int state) 42553e2822eSBjorn Andersson { 42653e2822eSBjorn Andersson struct qcom_smd_edge *edge = channel->edge; 42753e2822eSBjorn Andersson bool is_open = state == SMD_CHANNEL_OPENED; 42853e2822eSBjorn Andersson 42953e2822eSBjorn Andersson if (channel->state == state) 43053e2822eSBjorn Andersson return; 43153e2822eSBjorn Andersson 43253e2822eSBjorn Andersson dev_dbg(&edge->dev, "set_state(%s, %d)\n", channel->name, state); 43353e2822eSBjorn Andersson 43453e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fDSR, is_open); 43553e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fCTS, is_open); 43653e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fCD, is_open); 43753e2822eSBjorn Andersson 43853e2822eSBjorn Andersson SET_TX_CHANNEL_INFO(channel, state, state); 43953e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); 44053e2822eSBjorn Andersson 44153e2822eSBjorn Andersson channel->state = state; 44253e2822eSBjorn Andersson qcom_smd_signal_channel(channel); 44353e2822eSBjorn Andersson } 44453e2822eSBjorn Andersson 44553e2822eSBjorn Andersson /* 44653e2822eSBjorn Andersson * Copy count bytes of data using 32bit accesses, if that's required. 44753e2822eSBjorn Andersson */ 44853e2822eSBjorn Andersson static void smd_copy_to_fifo(void __iomem *dst, 44953e2822eSBjorn Andersson const void *src, 45053e2822eSBjorn Andersson size_t count, 45153e2822eSBjorn Andersson bool word_aligned) 45253e2822eSBjorn Andersson { 45353e2822eSBjorn Andersson if (word_aligned) { 45453e2822eSBjorn Andersson __iowrite32_copy(dst, src, count / sizeof(u32)); 45553e2822eSBjorn Andersson } else { 45653e2822eSBjorn Andersson memcpy_toio(dst, src, count); 45753e2822eSBjorn Andersson } 45853e2822eSBjorn Andersson } 45953e2822eSBjorn Andersson 46053e2822eSBjorn Andersson /* 46153e2822eSBjorn Andersson * Copy count bytes of data using 32bit accesses, if that is required. 46253e2822eSBjorn Andersson */ 46353e2822eSBjorn Andersson static void smd_copy_from_fifo(void *dst, 46453e2822eSBjorn Andersson const void __iomem *src, 46553e2822eSBjorn Andersson size_t count, 46653e2822eSBjorn Andersson bool word_aligned) 46753e2822eSBjorn Andersson { 46853e2822eSBjorn Andersson if (word_aligned) { 46953e2822eSBjorn Andersson __ioread32_copy(dst, src, count / sizeof(u32)); 47053e2822eSBjorn Andersson } else { 47153e2822eSBjorn Andersson memcpy_fromio(dst, src, count); 47253e2822eSBjorn Andersson } 47353e2822eSBjorn Andersson } 47453e2822eSBjorn Andersson 47553e2822eSBjorn Andersson /* 47653e2822eSBjorn Andersson * Read count bytes of data from the rx fifo into buf, but don't advance the 47753e2822eSBjorn Andersson * tail. 47853e2822eSBjorn Andersson */ 47953e2822eSBjorn Andersson static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel, 48053e2822eSBjorn Andersson void *buf, size_t count) 48153e2822eSBjorn Andersson { 48253e2822eSBjorn Andersson bool word_aligned; 48353e2822eSBjorn Andersson unsigned tail; 48453e2822eSBjorn Andersson size_t len; 48553e2822eSBjorn Andersson 48653e2822eSBjorn Andersson word_aligned = channel->info_word; 48753e2822eSBjorn Andersson tail = GET_RX_CHANNEL_INFO(channel, tail); 48853e2822eSBjorn Andersson 48953e2822eSBjorn Andersson len = min_t(size_t, count, channel->fifo_size - tail); 49053e2822eSBjorn Andersson if (len) { 49153e2822eSBjorn Andersson smd_copy_from_fifo(buf, 49253e2822eSBjorn Andersson channel->rx_fifo + tail, 49353e2822eSBjorn Andersson len, 49453e2822eSBjorn Andersson word_aligned); 49553e2822eSBjorn Andersson } 49653e2822eSBjorn Andersson 49753e2822eSBjorn Andersson if (len != count) { 49853e2822eSBjorn Andersson smd_copy_from_fifo(buf + len, 49953e2822eSBjorn Andersson channel->rx_fifo, 50053e2822eSBjorn Andersson count - len, 50153e2822eSBjorn Andersson word_aligned); 50253e2822eSBjorn Andersson } 50353e2822eSBjorn Andersson 50453e2822eSBjorn Andersson return count; 50553e2822eSBjorn Andersson } 50653e2822eSBjorn Andersson 50753e2822eSBjorn Andersson /* 50853e2822eSBjorn Andersson * Advance the rx tail by count bytes. 50953e2822eSBjorn Andersson */ 51053e2822eSBjorn Andersson static void qcom_smd_channel_advance(struct qcom_smd_channel *channel, 51153e2822eSBjorn Andersson size_t count) 51253e2822eSBjorn Andersson { 51353e2822eSBjorn Andersson unsigned tail; 51453e2822eSBjorn Andersson 51553e2822eSBjorn Andersson tail = GET_RX_CHANNEL_INFO(channel, tail); 51653e2822eSBjorn Andersson tail += count; 51753e2822eSBjorn Andersson tail &= (channel->fifo_size - 1); 51853e2822eSBjorn Andersson SET_RX_CHANNEL_INFO(channel, tail, tail); 51953e2822eSBjorn Andersson } 52053e2822eSBjorn Andersson 52153e2822eSBjorn Andersson /* 52253e2822eSBjorn Andersson * Read out a single packet from the rx fifo and deliver it to the device 52353e2822eSBjorn Andersson */ 52453e2822eSBjorn Andersson static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel) 52553e2822eSBjorn Andersson { 52653e2822eSBjorn Andersson struct rpmsg_endpoint *ept = &channel->qsept->ept; 52753e2822eSBjorn Andersson unsigned tail; 52853e2822eSBjorn Andersson size_t len; 52953e2822eSBjorn Andersson void *ptr; 53053e2822eSBjorn Andersson int ret; 53153e2822eSBjorn Andersson 53253e2822eSBjorn Andersson tail = GET_RX_CHANNEL_INFO(channel, tail); 53353e2822eSBjorn Andersson 53453e2822eSBjorn Andersson /* Use bounce buffer if the data wraps */ 53553e2822eSBjorn Andersson if (tail + channel->pkt_size >= channel->fifo_size) { 53653e2822eSBjorn Andersson ptr = channel->bounce_buffer; 53753e2822eSBjorn Andersson len = qcom_smd_channel_peek(channel, ptr, channel->pkt_size); 53853e2822eSBjorn Andersson } else { 53953e2822eSBjorn Andersson ptr = channel->rx_fifo + tail; 54053e2822eSBjorn Andersson len = channel->pkt_size; 54153e2822eSBjorn Andersson } 54253e2822eSBjorn Andersson 54353e2822eSBjorn Andersson ret = ept->cb(ept->rpdev, ptr, len, ept->priv, RPMSG_ADDR_ANY); 54453e2822eSBjorn Andersson if (ret < 0) 54553e2822eSBjorn Andersson return ret; 54653e2822eSBjorn Andersson 54753e2822eSBjorn Andersson /* Only forward the tail if the client consumed the data */ 54853e2822eSBjorn Andersson qcom_smd_channel_advance(channel, len); 54953e2822eSBjorn Andersson 55053e2822eSBjorn Andersson channel->pkt_size = 0; 55153e2822eSBjorn Andersson 55253e2822eSBjorn Andersson return 0; 55353e2822eSBjorn Andersson } 55453e2822eSBjorn Andersson 55553e2822eSBjorn Andersson /* 55653e2822eSBjorn Andersson * Per channel interrupt handling 55753e2822eSBjorn Andersson */ 55853e2822eSBjorn Andersson static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) 55953e2822eSBjorn Andersson { 56053e2822eSBjorn Andersson bool need_state_scan = false; 56153e2822eSBjorn Andersson int remote_state; 56253e2822eSBjorn Andersson __le32 pktlen; 56353e2822eSBjorn Andersson int avail; 56453e2822eSBjorn Andersson int ret; 56553e2822eSBjorn Andersson 56653e2822eSBjorn Andersson /* Handle state changes */ 56753e2822eSBjorn Andersson remote_state = GET_RX_CHANNEL_INFO(channel, state); 56853e2822eSBjorn Andersson if (remote_state != channel->remote_state) { 56953e2822eSBjorn Andersson channel->remote_state = remote_state; 57053e2822eSBjorn Andersson need_state_scan = true; 57153e2822eSBjorn Andersson } 57253e2822eSBjorn Andersson /* Indicate that we have seen any state change */ 57353e2822eSBjorn Andersson SET_RX_CHANNEL_FLAG(channel, fSTATE, 0); 57453e2822eSBjorn Andersson 57553e2822eSBjorn Andersson /* Signal waiting qcom_smd_send() about the interrupt */ 57653e2822eSBjorn Andersson if (!GET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) 57753e2822eSBjorn Andersson wake_up_interruptible(&channel->fblockread_event); 57853e2822eSBjorn Andersson 57953e2822eSBjorn Andersson /* Don't consume any data until we've opened the channel */ 58053e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_OPENED) 58153e2822eSBjorn Andersson goto out; 58253e2822eSBjorn Andersson 58353e2822eSBjorn Andersson /* Indicate that we've seen the new data */ 58453e2822eSBjorn Andersson SET_RX_CHANNEL_FLAG(channel, fHEAD, 0); 58553e2822eSBjorn Andersson 58653e2822eSBjorn Andersson /* Consume data */ 58753e2822eSBjorn Andersson for (;;) { 58853e2822eSBjorn Andersson avail = qcom_smd_channel_get_rx_avail(channel); 58953e2822eSBjorn Andersson 59053e2822eSBjorn Andersson if (!channel->pkt_size && avail >= SMD_PACKET_HEADER_LEN) { 59153e2822eSBjorn Andersson qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen)); 59253e2822eSBjorn Andersson qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN); 59353e2822eSBjorn Andersson channel->pkt_size = le32_to_cpu(pktlen); 59453e2822eSBjorn Andersson } else if (channel->pkt_size && avail >= channel->pkt_size) { 59553e2822eSBjorn Andersson ret = qcom_smd_channel_recv_single(channel); 59653e2822eSBjorn Andersson if (ret) 59753e2822eSBjorn Andersson break; 59853e2822eSBjorn Andersson } else { 59953e2822eSBjorn Andersson break; 60053e2822eSBjorn Andersson } 60153e2822eSBjorn Andersson } 60253e2822eSBjorn Andersson 60353e2822eSBjorn Andersson /* Indicate that we have seen and updated tail */ 60453e2822eSBjorn Andersson SET_RX_CHANNEL_FLAG(channel, fTAIL, 1); 60553e2822eSBjorn Andersson 60653e2822eSBjorn Andersson /* Signal the remote that we've consumed the data (if requested) */ 60753e2822eSBjorn Andersson if (!GET_RX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) { 60853e2822eSBjorn Andersson /* Ensure ordering of channel info updates */ 60953e2822eSBjorn Andersson wmb(); 61053e2822eSBjorn Andersson 61153e2822eSBjorn Andersson qcom_smd_signal_channel(channel); 61253e2822eSBjorn Andersson } 61353e2822eSBjorn Andersson 61453e2822eSBjorn Andersson out: 61553e2822eSBjorn Andersson return need_state_scan; 61653e2822eSBjorn Andersson } 61753e2822eSBjorn Andersson 61853e2822eSBjorn Andersson /* 61953e2822eSBjorn Andersson * The edge interrupts are triggered by the remote processor on state changes, 62053e2822eSBjorn Andersson * channel info updates or when new channels are created. 62153e2822eSBjorn Andersson */ 62253e2822eSBjorn Andersson static irqreturn_t qcom_smd_edge_intr(int irq, void *data) 62353e2822eSBjorn Andersson { 62453e2822eSBjorn Andersson struct qcom_smd_edge *edge = data; 62553e2822eSBjorn Andersson struct qcom_smd_channel *channel; 62653e2822eSBjorn Andersson unsigned available; 62753e2822eSBjorn Andersson bool kick_scanner = false; 62853e2822eSBjorn Andersson bool kick_state = false; 62953e2822eSBjorn Andersson 63053e2822eSBjorn Andersson /* 63153e2822eSBjorn Andersson * Handle state changes or data on each of the channels on this edge 63253e2822eSBjorn Andersson */ 63353e2822eSBjorn Andersson spin_lock(&edge->channels_lock); 63453e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 63553e2822eSBjorn Andersson spin_lock(&channel->recv_lock); 63653e2822eSBjorn Andersson kick_state |= qcom_smd_channel_intr(channel); 63753e2822eSBjorn Andersson spin_unlock(&channel->recv_lock); 63853e2822eSBjorn Andersson } 63953e2822eSBjorn Andersson spin_unlock(&edge->channels_lock); 64053e2822eSBjorn Andersson 64153e2822eSBjorn Andersson /* 64253e2822eSBjorn Andersson * Creating a new channel requires allocating an smem entry, so we only 64353e2822eSBjorn Andersson * have to scan if the amount of available space in smem have changed 64453e2822eSBjorn Andersson * since last scan. 64553e2822eSBjorn Andersson */ 64653e2822eSBjorn Andersson available = qcom_smem_get_free_space(edge->remote_pid); 64753e2822eSBjorn Andersson if (available != edge->smem_available) { 64853e2822eSBjorn Andersson edge->smem_available = available; 64953e2822eSBjorn Andersson kick_scanner = true; 65053e2822eSBjorn Andersson } 65153e2822eSBjorn Andersson 65253e2822eSBjorn Andersson if (kick_scanner) 65353e2822eSBjorn Andersson schedule_work(&edge->scan_work); 65453e2822eSBjorn Andersson if (kick_state) 65553e2822eSBjorn Andersson schedule_work(&edge->state_work); 65653e2822eSBjorn Andersson 65753e2822eSBjorn Andersson return IRQ_HANDLED; 65853e2822eSBjorn Andersson } 65953e2822eSBjorn Andersson 66053e2822eSBjorn Andersson /* 66153e2822eSBjorn Andersson * Calculate how much space is available in the tx fifo. 66253e2822eSBjorn Andersson */ 66353e2822eSBjorn Andersson static size_t qcom_smd_get_tx_avail(struct qcom_smd_channel *channel) 66453e2822eSBjorn Andersson { 66553e2822eSBjorn Andersson unsigned head; 66653e2822eSBjorn Andersson unsigned tail; 66753e2822eSBjorn Andersson unsigned mask = channel->fifo_size - 1; 66853e2822eSBjorn Andersson 66953e2822eSBjorn Andersson head = GET_TX_CHANNEL_INFO(channel, head); 67053e2822eSBjorn Andersson tail = GET_TX_CHANNEL_INFO(channel, tail); 67153e2822eSBjorn Andersson 67253e2822eSBjorn Andersson return mask - ((head - tail) & mask); 67353e2822eSBjorn Andersson } 67453e2822eSBjorn Andersson 67553e2822eSBjorn Andersson /* 67653e2822eSBjorn Andersson * Write count bytes of data into channel, possibly wrapping in the ring buffer 67753e2822eSBjorn Andersson */ 67853e2822eSBjorn Andersson static int qcom_smd_write_fifo(struct qcom_smd_channel *channel, 67953e2822eSBjorn Andersson const void *data, 68053e2822eSBjorn Andersson size_t count) 68153e2822eSBjorn Andersson { 68253e2822eSBjorn Andersson bool word_aligned; 68353e2822eSBjorn Andersson unsigned head; 68453e2822eSBjorn Andersson size_t len; 68553e2822eSBjorn Andersson 68653e2822eSBjorn Andersson word_aligned = channel->info_word; 68753e2822eSBjorn Andersson head = GET_TX_CHANNEL_INFO(channel, head); 68853e2822eSBjorn Andersson 68953e2822eSBjorn Andersson len = min_t(size_t, count, channel->fifo_size - head); 69053e2822eSBjorn Andersson if (len) { 69153e2822eSBjorn Andersson smd_copy_to_fifo(channel->tx_fifo + head, 69253e2822eSBjorn Andersson data, 69353e2822eSBjorn Andersson len, 69453e2822eSBjorn Andersson word_aligned); 69553e2822eSBjorn Andersson } 69653e2822eSBjorn Andersson 69753e2822eSBjorn Andersson if (len != count) { 69853e2822eSBjorn Andersson smd_copy_to_fifo(channel->tx_fifo, 69953e2822eSBjorn Andersson data + len, 70053e2822eSBjorn Andersson count - len, 70153e2822eSBjorn Andersson word_aligned); 70253e2822eSBjorn Andersson } 70353e2822eSBjorn Andersson 70453e2822eSBjorn Andersson head += count; 70553e2822eSBjorn Andersson head &= (channel->fifo_size - 1); 70653e2822eSBjorn Andersson SET_TX_CHANNEL_INFO(channel, head, head); 70753e2822eSBjorn Andersson 70853e2822eSBjorn Andersson return count; 70953e2822eSBjorn Andersson } 71053e2822eSBjorn Andersson 71153e2822eSBjorn Andersson /** 71253e2822eSBjorn Andersson * qcom_smd_send - write data to smd channel 71353e2822eSBjorn Andersson * @channel: channel handle 71453e2822eSBjorn Andersson * @data: buffer of data to write 71553e2822eSBjorn Andersson * @len: number of bytes to write 71653e2822eSBjorn Andersson * 71753e2822eSBjorn Andersson * This is a blocking write of len bytes into the channel's tx ring buffer and 71853e2822eSBjorn Andersson * signal the remote end. It will sleep until there is enough space available 71953e2822eSBjorn Andersson * in the tx buffer, utilizing the fBLOCKREADINTR signaling mechanism to avoid 72053e2822eSBjorn Andersson * polling. 72153e2822eSBjorn Andersson */ 72253e2822eSBjorn Andersson static int __qcom_smd_send(struct qcom_smd_channel *channel, const void *data, 72353e2822eSBjorn Andersson int len, bool wait) 72453e2822eSBjorn Andersson { 72553e2822eSBjorn Andersson __le32 hdr[5] = { cpu_to_le32(len), }; 72653e2822eSBjorn Andersson int tlen = sizeof(hdr) + len; 72753e2822eSBjorn Andersson int ret; 72853e2822eSBjorn Andersson 72953e2822eSBjorn Andersson /* Word aligned channels only accept word size aligned data */ 73053e2822eSBjorn Andersson if (channel->info_word && len % 4) 73153e2822eSBjorn Andersson return -EINVAL; 73253e2822eSBjorn Andersson 73353e2822eSBjorn Andersson /* Reject packets that are too big */ 73453e2822eSBjorn Andersson if (tlen >= channel->fifo_size) 73553e2822eSBjorn Andersson return -EINVAL; 73653e2822eSBjorn Andersson 73753e2822eSBjorn Andersson ret = mutex_lock_interruptible(&channel->tx_lock); 73853e2822eSBjorn Andersson if (ret) 73953e2822eSBjorn Andersson return ret; 74053e2822eSBjorn Andersson 74153e2822eSBjorn Andersson while (qcom_smd_get_tx_avail(channel) < tlen) { 74253e2822eSBjorn Andersson if (!wait) { 74353e2822eSBjorn Andersson ret = -ENOMEM; 74453e2822eSBjorn Andersson goto out; 74553e2822eSBjorn Andersson } 74653e2822eSBjorn Andersson 74753e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_OPENED) { 74853e2822eSBjorn Andersson ret = -EPIPE; 74953e2822eSBjorn Andersson goto out; 75053e2822eSBjorn Andersson } 75153e2822eSBjorn Andersson 75253e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 0); 75353e2822eSBjorn Andersson 75453e2822eSBjorn Andersson ret = wait_event_interruptible(channel->fblockread_event, 75553e2822eSBjorn Andersson qcom_smd_get_tx_avail(channel) >= tlen || 75653e2822eSBjorn Andersson channel->state != SMD_CHANNEL_OPENED); 75753e2822eSBjorn Andersson if (ret) 75853e2822eSBjorn Andersson goto out; 75953e2822eSBjorn Andersson 76053e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1); 76153e2822eSBjorn Andersson } 76253e2822eSBjorn Andersson 76353e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fTAIL, 0); 76453e2822eSBjorn Andersson 76553e2822eSBjorn Andersson qcom_smd_write_fifo(channel, hdr, sizeof(hdr)); 76653e2822eSBjorn Andersson qcom_smd_write_fifo(channel, data, len); 76753e2822eSBjorn Andersson 76853e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fHEAD, 1); 76953e2822eSBjorn Andersson 77053e2822eSBjorn Andersson /* Ensure ordering of channel info updates */ 77153e2822eSBjorn Andersson wmb(); 77253e2822eSBjorn Andersson 77353e2822eSBjorn Andersson qcom_smd_signal_channel(channel); 77453e2822eSBjorn Andersson 77553e2822eSBjorn Andersson out: 77653e2822eSBjorn Andersson mutex_unlock(&channel->tx_lock); 77753e2822eSBjorn Andersson 77853e2822eSBjorn Andersson return ret; 77953e2822eSBjorn Andersson } 78053e2822eSBjorn Andersson 78153e2822eSBjorn Andersson /* 78253e2822eSBjorn Andersson * Helper for opening a channel 78353e2822eSBjorn Andersson */ 78453e2822eSBjorn Andersson static int qcom_smd_channel_open(struct qcom_smd_channel *channel, 78553e2822eSBjorn Andersson rpmsg_rx_cb_t cb) 78653e2822eSBjorn Andersson { 78753e2822eSBjorn Andersson size_t bb_size; 78853e2822eSBjorn Andersson 78953e2822eSBjorn Andersson /* 79053e2822eSBjorn Andersson * Packets are maximum 4k, but reduce if the fifo is smaller 79153e2822eSBjorn Andersson */ 79253e2822eSBjorn Andersson bb_size = min(channel->fifo_size, SZ_4K); 79353e2822eSBjorn Andersson channel->bounce_buffer = kmalloc(bb_size, GFP_KERNEL); 79453e2822eSBjorn Andersson if (!channel->bounce_buffer) 79553e2822eSBjorn Andersson return -ENOMEM; 79653e2822eSBjorn Andersson 79753e2822eSBjorn Andersson qcom_smd_channel_set_callback(channel, cb); 79853e2822eSBjorn Andersson qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING); 79953e2822eSBjorn Andersson qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED); 80053e2822eSBjorn Andersson 80153e2822eSBjorn Andersson return 0; 80253e2822eSBjorn Andersson } 80353e2822eSBjorn Andersson 80453e2822eSBjorn Andersson /* 80553e2822eSBjorn Andersson * Helper for closing and resetting a channel 80653e2822eSBjorn Andersson */ 80753e2822eSBjorn Andersson static void qcom_smd_channel_close(struct qcom_smd_channel *channel) 80853e2822eSBjorn Andersson { 80953e2822eSBjorn Andersson qcom_smd_channel_set_callback(channel, NULL); 81053e2822eSBjorn Andersson 81153e2822eSBjorn Andersson kfree(channel->bounce_buffer); 81253e2822eSBjorn Andersson channel->bounce_buffer = NULL; 81353e2822eSBjorn Andersson 81453e2822eSBjorn Andersson qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); 81553e2822eSBjorn Andersson qcom_smd_channel_reset(channel); 81653e2822eSBjorn Andersson } 81753e2822eSBjorn Andersson 81853e2822eSBjorn Andersson static struct qcom_smd_channel * 81953e2822eSBjorn Andersson qcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name) 82053e2822eSBjorn Andersson { 82153e2822eSBjorn Andersson struct qcom_smd_channel *channel; 82253e2822eSBjorn Andersson struct qcom_smd_channel *ret = NULL; 82353e2822eSBjorn Andersson unsigned long flags; 82453e2822eSBjorn Andersson unsigned state; 82553e2822eSBjorn Andersson 82653e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 82753e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 82853e2822eSBjorn Andersson if (strcmp(channel->name, name)) 82953e2822eSBjorn Andersson continue; 83053e2822eSBjorn Andersson 83153e2822eSBjorn Andersson state = GET_RX_CHANNEL_INFO(channel, state); 83253e2822eSBjorn Andersson if (state != SMD_CHANNEL_OPENING && 83353e2822eSBjorn Andersson state != SMD_CHANNEL_OPENED) 83453e2822eSBjorn Andersson continue; 83553e2822eSBjorn Andersson 83653e2822eSBjorn Andersson ret = channel; 83753e2822eSBjorn Andersson break; 83853e2822eSBjorn Andersson } 83953e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 84053e2822eSBjorn Andersson 84153e2822eSBjorn Andersson return ret; 84253e2822eSBjorn Andersson } 84353e2822eSBjorn Andersson 84453e2822eSBjorn Andersson static void __ept_release(struct kref *kref) 84553e2822eSBjorn Andersson { 84653e2822eSBjorn Andersson struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint, 84753e2822eSBjorn Andersson refcount); 84853e2822eSBjorn Andersson kfree(to_smd_endpoint(ept)); 84953e2822eSBjorn Andersson } 85053e2822eSBjorn Andersson 85153e2822eSBjorn Andersson static struct rpmsg_endpoint *qcom_smd_create_ept(struct rpmsg_device *rpdev, 85253e2822eSBjorn Andersson rpmsg_rx_cb_t cb, void *priv, 85353e2822eSBjorn Andersson struct rpmsg_channel_info chinfo) 85453e2822eSBjorn Andersson { 85553e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept; 85653e2822eSBjorn Andersson struct qcom_smd_channel *channel; 85753e2822eSBjorn Andersson struct qcom_smd_device *qsdev = to_smd_device(rpdev); 85853e2822eSBjorn Andersson struct qcom_smd_edge *edge = qsdev->edge; 85953e2822eSBjorn Andersson struct rpmsg_endpoint *ept; 86053e2822eSBjorn Andersson const char *name = chinfo.name; 86153e2822eSBjorn Andersson int ret; 86253e2822eSBjorn Andersson 86353e2822eSBjorn Andersson /* Wait up to HZ for the channel to appear */ 86453e2822eSBjorn Andersson ret = wait_event_interruptible_timeout(edge->new_channel_event, 86553e2822eSBjorn Andersson (channel = qcom_smd_find_channel(edge, name)) != NULL, 86653e2822eSBjorn Andersson HZ); 86753e2822eSBjorn Andersson if (!ret) 86853e2822eSBjorn Andersson return NULL; 86953e2822eSBjorn Andersson 87053e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_CLOSED) { 87153e2822eSBjorn Andersson dev_err(&rpdev->dev, "channel %s is busy\n", channel->name); 87253e2822eSBjorn Andersson return NULL; 87353e2822eSBjorn Andersson } 87453e2822eSBjorn Andersson 87553e2822eSBjorn Andersson qsept = kzalloc(sizeof(*qsept), GFP_KERNEL); 87653e2822eSBjorn Andersson if (!qsept) 87753e2822eSBjorn Andersson return NULL; 87853e2822eSBjorn Andersson 87953e2822eSBjorn Andersson ept = &qsept->ept; 88053e2822eSBjorn Andersson 88153e2822eSBjorn Andersson kref_init(&ept->refcount); 88253e2822eSBjorn Andersson 88353e2822eSBjorn Andersson ept->rpdev = rpdev; 88453e2822eSBjorn Andersson ept->cb = cb; 88553e2822eSBjorn Andersson ept->priv = priv; 88653e2822eSBjorn Andersson ept->ops = &qcom_smd_endpoint_ops; 88753e2822eSBjorn Andersson 88853e2822eSBjorn Andersson channel->qsept = qsept; 88953e2822eSBjorn Andersson qsept->qsch = channel; 89053e2822eSBjorn Andersson 89153e2822eSBjorn Andersson ret = qcom_smd_channel_open(channel, cb); 89253e2822eSBjorn Andersson if (ret) 89353e2822eSBjorn Andersson goto free_ept; 89453e2822eSBjorn Andersson 89553e2822eSBjorn Andersson return ept; 89653e2822eSBjorn Andersson 89753e2822eSBjorn Andersson free_ept: 89853e2822eSBjorn Andersson channel->qsept = NULL; 89953e2822eSBjorn Andersson kref_put(&ept->refcount, __ept_release); 90053e2822eSBjorn Andersson return NULL; 90153e2822eSBjorn Andersson } 90253e2822eSBjorn Andersson 90353e2822eSBjorn Andersson static void qcom_smd_destroy_ept(struct rpmsg_endpoint *ept) 90453e2822eSBjorn Andersson { 90553e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 90653e2822eSBjorn Andersson struct qcom_smd_channel *ch = qsept->qsch; 90753e2822eSBjorn Andersson 90853e2822eSBjorn Andersson qcom_smd_channel_close(ch); 90953e2822eSBjorn Andersson ch->qsept = NULL; 91053e2822eSBjorn Andersson kref_put(&ept->refcount, __ept_release); 91153e2822eSBjorn Andersson } 91253e2822eSBjorn Andersson 91353e2822eSBjorn Andersson static int qcom_smd_send(struct rpmsg_endpoint *ept, void *data, int len) 91453e2822eSBjorn Andersson { 91553e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 91653e2822eSBjorn Andersson 91753e2822eSBjorn Andersson return __qcom_smd_send(qsept->qsch, data, len, true); 91853e2822eSBjorn Andersson } 91953e2822eSBjorn Andersson 92053e2822eSBjorn Andersson static int qcom_smd_trysend(struct rpmsg_endpoint *ept, void *data, int len) 92153e2822eSBjorn Andersson { 92253e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 92353e2822eSBjorn Andersson 92453e2822eSBjorn Andersson return __qcom_smd_send(qsept->qsch, data, len, false); 92553e2822eSBjorn Andersson } 92653e2822eSBjorn Andersson 92753e2822eSBjorn Andersson /* 92853e2822eSBjorn Andersson * Finds the device_node for the smd child interested in this channel. 92953e2822eSBjorn Andersson */ 93053e2822eSBjorn Andersson static struct device_node *qcom_smd_match_channel(struct device_node *edge_node, 93153e2822eSBjorn Andersson const char *channel) 93253e2822eSBjorn Andersson { 93353e2822eSBjorn Andersson struct device_node *child; 93453e2822eSBjorn Andersson const char *name; 93553e2822eSBjorn Andersson const char *key; 93653e2822eSBjorn Andersson int ret; 93753e2822eSBjorn Andersson 93853e2822eSBjorn Andersson for_each_available_child_of_node(edge_node, child) { 93953e2822eSBjorn Andersson key = "qcom,smd-channels"; 94053e2822eSBjorn Andersson ret = of_property_read_string(child, key, &name); 94153e2822eSBjorn Andersson if (ret) 94253e2822eSBjorn Andersson continue; 94353e2822eSBjorn Andersson 94453e2822eSBjorn Andersson if (strcmp(name, channel) == 0) 94553e2822eSBjorn Andersson return child; 94653e2822eSBjorn Andersson } 94753e2822eSBjorn Andersson 94853e2822eSBjorn Andersson return NULL; 94953e2822eSBjorn Andersson } 95053e2822eSBjorn Andersson 95153e2822eSBjorn Andersson static const struct rpmsg_device_ops qcom_smd_device_ops = { 95253e2822eSBjorn Andersson .create_ept = qcom_smd_create_ept, 95353e2822eSBjorn Andersson }; 95453e2822eSBjorn Andersson 95553e2822eSBjorn Andersson static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops = { 95653e2822eSBjorn Andersson .destroy_ept = qcom_smd_destroy_ept, 95753e2822eSBjorn Andersson .send = qcom_smd_send, 95853e2822eSBjorn Andersson .trysend = qcom_smd_trysend, 95953e2822eSBjorn Andersson }; 96053e2822eSBjorn Andersson 96153e2822eSBjorn Andersson /* 96253e2822eSBjorn Andersson * Create a smd client device for channel that is being opened. 96353e2822eSBjorn Andersson */ 96453e2822eSBjorn Andersson static int qcom_smd_create_device(struct qcom_smd_channel *channel) 96553e2822eSBjorn Andersson { 96653e2822eSBjorn Andersson struct qcom_smd_device *qsdev; 96753e2822eSBjorn Andersson struct rpmsg_device *rpdev; 96853e2822eSBjorn Andersson struct qcom_smd_edge *edge = channel->edge; 96953e2822eSBjorn Andersson 97053e2822eSBjorn Andersson dev_dbg(&edge->dev, "registering '%s'\n", channel->name); 97153e2822eSBjorn Andersson 97253e2822eSBjorn Andersson qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL); 97353e2822eSBjorn Andersson if (!qsdev) 97453e2822eSBjorn Andersson return -ENOMEM; 97553e2822eSBjorn Andersson 97653e2822eSBjorn Andersson /* Link qsdev to our SMD edge */ 97753e2822eSBjorn Andersson qsdev->edge = edge; 97853e2822eSBjorn Andersson 97953e2822eSBjorn Andersson /* Assign callbacks for rpmsg_device */ 98053e2822eSBjorn Andersson qsdev->rpdev.ops = &qcom_smd_device_ops; 98153e2822eSBjorn Andersson 98253e2822eSBjorn Andersson /* Assign public information to the rpmsg_device */ 98353e2822eSBjorn Andersson rpdev = &qsdev->rpdev; 98453e2822eSBjorn Andersson strncpy(rpdev->id.name, channel->name, RPMSG_NAME_SIZE); 98553e2822eSBjorn Andersson rpdev->src = RPMSG_ADDR_ANY; 98653e2822eSBjorn Andersson rpdev->dst = RPMSG_ADDR_ANY; 98753e2822eSBjorn Andersson 98853e2822eSBjorn Andersson rpdev->dev.of_node = qcom_smd_match_channel(edge->of_node, channel->name); 98953e2822eSBjorn Andersson rpdev->dev.parent = &edge->dev; 99053e2822eSBjorn Andersson 99153e2822eSBjorn Andersson return rpmsg_register_device(rpdev); 99253e2822eSBjorn Andersson } 99353e2822eSBjorn Andersson 99453e2822eSBjorn Andersson /* 99553e2822eSBjorn Andersson * Allocate the qcom_smd_channel object for a newly found smd channel, 99653e2822eSBjorn Andersson * retrieving and validating the smem items involved. 99753e2822eSBjorn Andersson */ 99853e2822eSBjorn Andersson static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *edge, 99953e2822eSBjorn Andersson unsigned smem_info_item, 100053e2822eSBjorn Andersson unsigned smem_fifo_item, 100153e2822eSBjorn Andersson char *name) 100253e2822eSBjorn Andersson { 100353e2822eSBjorn Andersson struct qcom_smd_channel *channel; 100453e2822eSBjorn Andersson size_t fifo_size; 100553e2822eSBjorn Andersson size_t info_size; 100653e2822eSBjorn Andersson void *fifo_base; 100753e2822eSBjorn Andersson void *info; 100853e2822eSBjorn Andersson int ret; 100953e2822eSBjorn Andersson 101053e2822eSBjorn Andersson channel = devm_kzalloc(&edge->dev, sizeof(*channel), GFP_KERNEL); 101153e2822eSBjorn Andersson if (!channel) 101253e2822eSBjorn Andersson return ERR_PTR(-ENOMEM); 101353e2822eSBjorn Andersson 101453e2822eSBjorn Andersson channel->edge = edge; 101553e2822eSBjorn Andersson channel->name = devm_kstrdup(&edge->dev, name, GFP_KERNEL); 101653e2822eSBjorn Andersson if (!channel->name) 101753e2822eSBjorn Andersson return ERR_PTR(-ENOMEM); 101853e2822eSBjorn Andersson 101953e2822eSBjorn Andersson mutex_init(&channel->tx_lock); 102053e2822eSBjorn Andersson spin_lock_init(&channel->recv_lock); 102153e2822eSBjorn Andersson init_waitqueue_head(&channel->fblockread_event); 102253e2822eSBjorn Andersson 102353e2822eSBjorn Andersson info = qcom_smem_get(edge->remote_pid, smem_info_item, &info_size); 102453e2822eSBjorn Andersson if (IS_ERR(info)) { 102553e2822eSBjorn Andersson ret = PTR_ERR(info); 102653e2822eSBjorn Andersson goto free_name_and_channel; 102753e2822eSBjorn Andersson } 102853e2822eSBjorn Andersson 102953e2822eSBjorn Andersson /* 103053e2822eSBjorn Andersson * Use the size of the item to figure out which channel info struct to 103153e2822eSBjorn Andersson * use. 103253e2822eSBjorn Andersson */ 103353e2822eSBjorn Andersson if (info_size == 2 * sizeof(struct smd_channel_info_word)) { 103453e2822eSBjorn Andersson channel->info_word = info; 103553e2822eSBjorn Andersson } else if (info_size == 2 * sizeof(struct smd_channel_info)) { 103653e2822eSBjorn Andersson channel->info = info; 103753e2822eSBjorn Andersson } else { 103853e2822eSBjorn Andersson dev_err(&edge->dev, 103953e2822eSBjorn Andersson "channel info of size %zu not supported\n", info_size); 104053e2822eSBjorn Andersson ret = -EINVAL; 104153e2822eSBjorn Andersson goto free_name_and_channel; 104253e2822eSBjorn Andersson } 104353e2822eSBjorn Andersson 104453e2822eSBjorn Andersson fifo_base = qcom_smem_get(edge->remote_pid, smem_fifo_item, &fifo_size); 104553e2822eSBjorn Andersson if (IS_ERR(fifo_base)) { 104653e2822eSBjorn Andersson ret = PTR_ERR(fifo_base); 104753e2822eSBjorn Andersson goto free_name_and_channel; 104853e2822eSBjorn Andersson } 104953e2822eSBjorn Andersson 105053e2822eSBjorn Andersson /* The channel consist of a rx and tx fifo of equal size */ 105153e2822eSBjorn Andersson fifo_size /= 2; 105253e2822eSBjorn Andersson 105353e2822eSBjorn Andersson dev_dbg(&edge->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n", 105453e2822eSBjorn Andersson name, info_size, fifo_size); 105553e2822eSBjorn Andersson 105653e2822eSBjorn Andersson channel->tx_fifo = fifo_base; 105753e2822eSBjorn Andersson channel->rx_fifo = fifo_base + fifo_size; 105853e2822eSBjorn Andersson channel->fifo_size = fifo_size; 105953e2822eSBjorn Andersson 106053e2822eSBjorn Andersson qcom_smd_channel_reset(channel); 106153e2822eSBjorn Andersson 106253e2822eSBjorn Andersson return channel; 106353e2822eSBjorn Andersson 106453e2822eSBjorn Andersson free_name_and_channel: 106553e2822eSBjorn Andersson devm_kfree(&edge->dev, channel->name); 106653e2822eSBjorn Andersson devm_kfree(&edge->dev, channel); 106753e2822eSBjorn Andersson 106853e2822eSBjorn Andersson return ERR_PTR(ret); 106953e2822eSBjorn Andersson } 107053e2822eSBjorn Andersson 107153e2822eSBjorn Andersson /* 107253e2822eSBjorn Andersson * Scans the allocation table for any newly allocated channels, calls 107353e2822eSBjorn Andersson * qcom_smd_create_channel() to create representations of these and add 107453e2822eSBjorn Andersson * them to the edge's list of channels. 107553e2822eSBjorn Andersson */ 107653e2822eSBjorn Andersson static void qcom_channel_scan_worker(struct work_struct *work) 107753e2822eSBjorn Andersson { 107853e2822eSBjorn Andersson struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work); 107953e2822eSBjorn Andersson struct qcom_smd_alloc_entry *alloc_tbl; 108053e2822eSBjorn Andersson struct qcom_smd_alloc_entry *entry; 108153e2822eSBjorn Andersson struct qcom_smd_channel *channel; 108253e2822eSBjorn Andersson unsigned long flags; 108353e2822eSBjorn Andersson unsigned fifo_id; 108453e2822eSBjorn Andersson unsigned info_id; 108553e2822eSBjorn Andersson int tbl; 108653e2822eSBjorn Andersson int i; 108753e2822eSBjorn Andersson u32 eflags, cid; 108853e2822eSBjorn Andersson 108953e2822eSBjorn Andersson for (tbl = 0; tbl < SMD_ALLOC_TBL_COUNT; tbl++) { 109053e2822eSBjorn Andersson alloc_tbl = qcom_smem_get(edge->remote_pid, 109153e2822eSBjorn Andersson smem_items[tbl].alloc_tbl_id, NULL); 109253e2822eSBjorn Andersson if (IS_ERR(alloc_tbl)) 109353e2822eSBjorn Andersson continue; 109453e2822eSBjorn Andersson 109553e2822eSBjorn Andersson for (i = 0; i < SMD_ALLOC_TBL_SIZE; i++) { 109653e2822eSBjorn Andersson entry = &alloc_tbl[i]; 109753e2822eSBjorn Andersson eflags = le32_to_cpu(entry->flags); 109853e2822eSBjorn Andersson if (test_bit(i, edge->allocated[tbl])) 109953e2822eSBjorn Andersson continue; 110053e2822eSBjorn Andersson 110153e2822eSBjorn Andersson if (entry->ref_count == 0) 110253e2822eSBjorn Andersson continue; 110353e2822eSBjorn Andersson 110453e2822eSBjorn Andersson if (!entry->name[0]) 110553e2822eSBjorn Andersson continue; 110653e2822eSBjorn Andersson 110753e2822eSBjorn Andersson if (!(eflags & SMD_CHANNEL_FLAGS_PACKET)) 110853e2822eSBjorn Andersson continue; 110953e2822eSBjorn Andersson 111053e2822eSBjorn Andersson if ((eflags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id) 111153e2822eSBjorn Andersson continue; 111253e2822eSBjorn Andersson 111353e2822eSBjorn Andersson cid = le32_to_cpu(entry->cid); 111453e2822eSBjorn Andersson info_id = smem_items[tbl].info_base_id + cid; 111553e2822eSBjorn Andersson fifo_id = smem_items[tbl].fifo_base_id + cid; 111653e2822eSBjorn Andersson 111753e2822eSBjorn Andersson channel = qcom_smd_create_channel(edge, info_id, fifo_id, entry->name); 111853e2822eSBjorn Andersson if (IS_ERR(channel)) 111953e2822eSBjorn Andersson continue; 112053e2822eSBjorn Andersson 112153e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 112253e2822eSBjorn Andersson list_add(&channel->list, &edge->channels); 112353e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 112453e2822eSBjorn Andersson 112553e2822eSBjorn Andersson dev_dbg(&edge->dev, "new channel found: '%s'\n", channel->name); 112653e2822eSBjorn Andersson set_bit(i, edge->allocated[tbl]); 112753e2822eSBjorn Andersson 112853e2822eSBjorn Andersson wake_up_interruptible(&edge->new_channel_event); 112953e2822eSBjorn Andersson } 113053e2822eSBjorn Andersson } 113153e2822eSBjorn Andersson 113253e2822eSBjorn Andersson schedule_work(&edge->state_work); 113353e2822eSBjorn Andersson } 113453e2822eSBjorn Andersson 113553e2822eSBjorn Andersson /* 113653e2822eSBjorn Andersson * This per edge worker scans smem for any new channels and register these. It 113753e2822eSBjorn Andersson * then scans all registered channels for state changes that should be handled 113853e2822eSBjorn Andersson * by creating or destroying smd client devices for the registered channels. 113953e2822eSBjorn Andersson * 114053e2822eSBjorn Andersson * LOCKING: edge->channels_lock only needs to cover the list operations, as the 114153e2822eSBjorn Andersson * worker is killed before any channels are deallocated 114253e2822eSBjorn Andersson */ 114353e2822eSBjorn Andersson static void qcom_channel_state_worker(struct work_struct *work) 114453e2822eSBjorn Andersson { 114553e2822eSBjorn Andersson struct qcom_smd_channel *channel; 114653e2822eSBjorn Andersson struct qcom_smd_edge *edge = container_of(work, 114753e2822eSBjorn Andersson struct qcom_smd_edge, 114853e2822eSBjorn Andersson state_work); 114953e2822eSBjorn Andersson struct rpmsg_channel_info chinfo; 115053e2822eSBjorn Andersson unsigned remote_state; 115153e2822eSBjorn Andersson unsigned long flags; 115253e2822eSBjorn Andersson 115353e2822eSBjorn Andersson /* 115453e2822eSBjorn Andersson * Register a device for any closed channel where the remote processor 115553e2822eSBjorn Andersson * is showing interest in opening the channel. 115653e2822eSBjorn Andersson */ 115753e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 115853e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 115953e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_CLOSED) 116053e2822eSBjorn Andersson continue; 116153e2822eSBjorn Andersson 116253e2822eSBjorn Andersson remote_state = GET_RX_CHANNEL_INFO(channel, state); 116353e2822eSBjorn Andersson if (remote_state != SMD_CHANNEL_OPENING && 116453e2822eSBjorn Andersson remote_state != SMD_CHANNEL_OPENED) 116553e2822eSBjorn Andersson continue; 116653e2822eSBjorn Andersson 116753e2822eSBjorn Andersson if (channel->registered) 116853e2822eSBjorn Andersson continue; 116953e2822eSBjorn Andersson 117053e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 117153e2822eSBjorn Andersson qcom_smd_create_device(channel); 117253e2822eSBjorn Andersson channel->registered = true; 117353e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 117453e2822eSBjorn Andersson 117553e2822eSBjorn Andersson channel->registered = true; 117653e2822eSBjorn Andersson } 117753e2822eSBjorn Andersson 117853e2822eSBjorn Andersson /* 117953e2822eSBjorn Andersson * Unregister the device for any channel that is opened where the 118053e2822eSBjorn Andersson * remote processor is closing the channel. 118153e2822eSBjorn Andersson */ 118253e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 118353e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_OPENING && 118453e2822eSBjorn Andersson channel->state != SMD_CHANNEL_OPENED) 118553e2822eSBjorn Andersson continue; 118653e2822eSBjorn Andersson 118753e2822eSBjorn Andersson remote_state = GET_RX_CHANNEL_INFO(channel, state); 118853e2822eSBjorn Andersson if (remote_state == SMD_CHANNEL_OPENING || 118953e2822eSBjorn Andersson remote_state == SMD_CHANNEL_OPENED) 119053e2822eSBjorn Andersson continue; 119153e2822eSBjorn Andersson 119253e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 119353e2822eSBjorn Andersson 119453e2822eSBjorn Andersson strncpy(chinfo.name, channel->name, sizeof(chinfo.name)); 119553e2822eSBjorn Andersson chinfo.src = RPMSG_ADDR_ANY; 119653e2822eSBjorn Andersson chinfo.dst = RPMSG_ADDR_ANY; 119753e2822eSBjorn Andersson rpmsg_unregister_device(&edge->dev, &chinfo); 119853e2822eSBjorn Andersson channel->registered = false; 119953e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 120053e2822eSBjorn Andersson } 120153e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 120253e2822eSBjorn Andersson } 120353e2822eSBjorn Andersson 120453e2822eSBjorn Andersson /* 120553e2822eSBjorn Andersson * Parses an of_node describing an edge. 120653e2822eSBjorn Andersson */ 120753e2822eSBjorn Andersson static int qcom_smd_parse_edge(struct device *dev, 120853e2822eSBjorn Andersson struct device_node *node, 120953e2822eSBjorn Andersson struct qcom_smd_edge *edge) 121053e2822eSBjorn Andersson { 121153e2822eSBjorn Andersson struct device_node *syscon_np; 121253e2822eSBjorn Andersson const char *key; 121353e2822eSBjorn Andersson int irq; 121453e2822eSBjorn Andersson int ret; 121553e2822eSBjorn Andersson 121653e2822eSBjorn Andersson INIT_LIST_HEAD(&edge->channels); 121753e2822eSBjorn Andersson spin_lock_init(&edge->channels_lock); 121853e2822eSBjorn Andersson 121953e2822eSBjorn Andersson INIT_WORK(&edge->scan_work, qcom_channel_scan_worker); 122053e2822eSBjorn Andersson INIT_WORK(&edge->state_work, qcom_channel_state_worker); 122153e2822eSBjorn Andersson 122253e2822eSBjorn Andersson edge->of_node = of_node_get(node); 122353e2822eSBjorn Andersson 122453e2822eSBjorn Andersson key = "qcom,smd-edge"; 122553e2822eSBjorn Andersson ret = of_property_read_u32(node, key, &edge->edge_id); 122653e2822eSBjorn Andersson if (ret) { 122753e2822eSBjorn Andersson dev_err(dev, "edge missing %s property\n", key); 122853e2822eSBjorn Andersson return -EINVAL; 122953e2822eSBjorn Andersson } 123053e2822eSBjorn Andersson 123153e2822eSBjorn Andersson edge->remote_pid = QCOM_SMEM_HOST_ANY; 123253e2822eSBjorn Andersson key = "qcom,remote-pid"; 123353e2822eSBjorn Andersson of_property_read_u32(node, key, &edge->remote_pid); 123453e2822eSBjorn Andersson 123553e2822eSBjorn Andersson syscon_np = of_parse_phandle(node, "qcom,ipc", 0); 123653e2822eSBjorn Andersson if (!syscon_np) { 123753e2822eSBjorn Andersson dev_err(dev, "no qcom,ipc node\n"); 123853e2822eSBjorn Andersson return -ENODEV; 123953e2822eSBjorn Andersson } 124053e2822eSBjorn Andersson 124153e2822eSBjorn Andersson edge->ipc_regmap = syscon_node_to_regmap(syscon_np); 124253e2822eSBjorn Andersson if (IS_ERR(edge->ipc_regmap)) 124353e2822eSBjorn Andersson return PTR_ERR(edge->ipc_regmap); 124453e2822eSBjorn Andersson 124553e2822eSBjorn Andersson key = "qcom,ipc"; 124653e2822eSBjorn Andersson ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset); 124753e2822eSBjorn Andersson if (ret < 0) { 124853e2822eSBjorn Andersson dev_err(dev, "no offset in %s\n", key); 124953e2822eSBjorn Andersson return -EINVAL; 125053e2822eSBjorn Andersson } 125153e2822eSBjorn Andersson 125253e2822eSBjorn Andersson ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit); 125353e2822eSBjorn Andersson if (ret < 0) { 125453e2822eSBjorn Andersson dev_err(dev, "no bit in %s\n", key); 125553e2822eSBjorn Andersson return -EINVAL; 125653e2822eSBjorn Andersson } 125753e2822eSBjorn Andersson 125853e2822eSBjorn Andersson irq = irq_of_parse_and_map(node, 0); 125953e2822eSBjorn Andersson if (irq < 0) { 126053e2822eSBjorn Andersson dev_err(dev, "required smd interrupt missing\n"); 126153e2822eSBjorn Andersson return -EINVAL; 126253e2822eSBjorn Andersson } 126353e2822eSBjorn Andersson 126453e2822eSBjorn Andersson ret = devm_request_irq(dev, irq, 126553e2822eSBjorn Andersson qcom_smd_edge_intr, IRQF_TRIGGER_RISING, 126653e2822eSBjorn Andersson node->name, edge); 126753e2822eSBjorn Andersson if (ret) { 126853e2822eSBjorn Andersson dev_err(dev, "failed to request smd irq\n"); 126953e2822eSBjorn Andersson return ret; 127053e2822eSBjorn Andersson } 127153e2822eSBjorn Andersson 127253e2822eSBjorn Andersson edge->irq = irq; 127353e2822eSBjorn Andersson 127453e2822eSBjorn Andersson return 0; 127553e2822eSBjorn Andersson } 127653e2822eSBjorn Andersson 127753e2822eSBjorn Andersson /* 127853e2822eSBjorn Andersson * Release function for an edge. 127953e2822eSBjorn Andersson * Reset the state of each associated channel and free the edge context. 128053e2822eSBjorn Andersson */ 128153e2822eSBjorn Andersson static void qcom_smd_edge_release(struct device *dev) 128253e2822eSBjorn Andersson { 128353e2822eSBjorn Andersson struct qcom_smd_channel *channel; 128453e2822eSBjorn Andersson struct qcom_smd_edge *edge = to_smd_edge(dev); 128553e2822eSBjorn Andersson 128653e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 128753e2822eSBjorn Andersson SET_RX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED); 128853e2822eSBjorn Andersson SET_RX_CHANNEL_INFO(channel, head, 0); 128953e2822eSBjorn Andersson SET_RX_CHANNEL_INFO(channel, tail, 0); 129053e2822eSBjorn Andersson } 129153e2822eSBjorn Andersson 129253e2822eSBjorn Andersson kfree(edge); 129353e2822eSBjorn Andersson } 129453e2822eSBjorn Andersson 129553e2822eSBjorn Andersson /** 129653e2822eSBjorn Andersson * qcom_smd_register_edge() - register an edge based on an device_node 129753e2822eSBjorn Andersson * @parent: parent device for the edge 129853e2822eSBjorn Andersson * @node: device_node describing the edge 129953e2822eSBjorn Andersson * 130053e2822eSBjorn Andersson * Returns an edge reference, or negative ERR_PTR() on failure. 130153e2822eSBjorn Andersson */ 130253e2822eSBjorn Andersson struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent, 130353e2822eSBjorn Andersson struct device_node *node) 130453e2822eSBjorn Andersson { 130553e2822eSBjorn Andersson struct qcom_smd_edge *edge; 130653e2822eSBjorn Andersson int ret; 130753e2822eSBjorn Andersson 130853e2822eSBjorn Andersson edge = kzalloc(sizeof(*edge), GFP_KERNEL); 130953e2822eSBjorn Andersson if (!edge) 131053e2822eSBjorn Andersson return ERR_PTR(-ENOMEM); 131153e2822eSBjorn Andersson 131253e2822eSBjorn Andersson init_waitqueue_head(&edge->new_channel_event); 131353e2822eSBjorn Andersson 131453e2822eSBjorn Andersson edge->dev.parent = parent; 131553e2822eSBjorn Andersson edge->dev.release = qcom_smd_edge_release; 131653e2822eSBjorn Andersson dev_set_name(&edge->dev, "%s:%s", dev_name(parent), node->name); 131753e2822eSBjorn Andersson ret = device_register(&edge->dev); 131853e2822eSBjorn Andersson if (ret) { 131953e2822eSBjorn Andersson pr_err("failed to register smd edge\n"); 132053e2822eSBjorn Andersson return ERR_PTR(ret); 132153e2822eSBjorn Andersson } 132253e2822eSBjorn Andersson 132353e2822eSBjorn Andersson ret = qcom_smd_parse_edge(&edge->dev, node, edge); 132453e2822eSBjorn Andersson if (ret) { 132553e2822eSBjorn Andersson dev_err(&edge->dev, "failed to parse smd edge\n"); 132653e2822eSBjorn Andersson goto unregister_dev; 132753e2822eSBjorn Andersson } 132853e2822eSBjorn Andersson 132953e2822eSBjorn Andersson schedule_work(&edge->scan_work); 133053e2822eSBjorn Andersson 133153e2822eSBjorn Andersson return edge; 133253e2822eSBjorn Andersson 133353e2822eSBjorn Andersson unregister_dev: 133453e2822eSBjorn Andersson put_device(&edge->dev); 133553e2822eSBjorn Andersson return ERR_PTR(ret); 133653e2822eSBjorn Andersson } 133753e2822eSBjorn Andersson EXPORT_SYMBOL(qcom_smd_register_edge); 133853e2822eSBjorn Andersson 133953e2822eSBjorn Andersson static int qcom_smd_remove_device(struct device *dev, void *data) 134053e2822eSBjorn Andersson { 134153e2822eSBjorn Andersson device_unregister(dev); 134253e2822eSBjorn Andersson 134353e2822eSBjorn Andersson return 0; 134453e2822eSBjorn Andersson } 134553e2822eSBjorn Andersson 134653e2822eSBjorn Andersson /** 134753e2822eSBjorn Andersson * qcom_smd_unregister_edge() - release an edge and its children 134853e2822eSBjorn Andersson * @edge: edge reference acquired from qcom_smd_register_edge 134953e2822eSBjorn Andersson */ 135053e2822eSBjorn Andersson int qcom_smd_unregister_edge(struct qcom_smd_edge *edge) 135153e2822eSBjorn Andersson { 135253e2822eSBjorn Andersson int ret; 135353e2822eSBjorn Andersson 135453e2822eSBjorn Andersson disable_irq(edge->irq); 135553e2822eSBjorn Andersson cancel_work_sync(&edge->scan_work); 135653e2822eSBjorn Andersson cancel_work_sync(&edge->state_work); 135753e2822eSBjorn Andersson 135853e2822eSBjorn Andersson ret = device_for_each_child(&edge->dev, NULL, qcom_smd_remove_device); 135953e2822eSBjorn Andersson if (ret) 136053e2822eSBjorn Andersson dev_warn(&edge->dev, "can't remove smd device: %d\n", ret); 136153e2822eSBjorn Andersson 136253e2822eSBjorn Andersson device_unregister(&edge->dev); 136353e2822eSBjorn Andersson 136453e2822eSBjorn Andersson return 0; 136553e2822eSBjorn Andersson } 136653e2822eSBjorn Andersson EXPORT_SYMBOL(qcom_smd_unregister_edge); 136753e2822eSBjorn Andersson 136853e2822eSBjorn Andersson static int qcom_smd_probe(struct platform_device *pdev) 136953e2822eSBjorn Andersson { 137053e2822eSBjorn Andersson struct device_node *node; 137153e2822eSBjorn Andersson void *p; 137253e2822eSBjorn Andersson 137353e2822eSBjorn Andersson /* Wait for smem */ 137453e2822eSBjorn Andersson p = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[0].alloc_tbl_id, NULL); 137553e2822eSBjorn Andersson if (PTR_ERR(p) == -EPROBE_DEFER) 137653e2822eSBjorn Andersson return PTR_ERR(p); 137753e2822eSBjorn Andersson 137853e2822eSBjorn Andersson for_each_available_child_of_node(pdev->dev.of_node, node) 137953e2822eSBjorn Andersson qcom_smd_register_edge(&pdev->dev, node); 138053e2822eSBjorn Andersson 138153e2822eSBjorn Andersson return 0; 138253e2822eSBjorn Andersson } 138353e2822eSBjorn Andersson 138453e2822eSBjorn Andersson static int qcom_smd_remove_edge(struct device *dev, void *data) 138553e2822eSBjorn Andersson { 138653e2822eSBjorn Andersson struct qcom_smd_edge *edge = to_smd_edge(dev); 138753e2822eSBjorn Andersson 138853e2822eSBjorn Andersson return qcom_smd_unregister_edge(edge); 138953e2822eSBjorn Andersson } 139053e2822eSBjorn Andersson 139153e2822eSBjorn Andersson /* 139253e2822eSBjorn Andersson * Shut down all smd clients by making sure that each edge stops processing 139353e2822eSBjorn Andersson * events and scanning for new channels, then call destroy on the devices. 139453e2822eSBjorn Andersson */ 139553e2822eSBjorn Andersson static int qcom_smd_remove(struct platform_device *pdev) 139653e2822eSBjorn Andersson { 139753e2822eSBjorn Andersson int ret; 139853e2822eSBjorn Andersson 139953e2822eSBjorn Andersson ret = device_for_each_child(&pdev->dev, NULL, qcom_smd_remove_edge); 140053e2822eSBjorn Andersson if (ret) 140153e2822eSBjorn Andersson dev_warn(&pdev->dev, "can't remove smd device: %d\n", ret); 140253e2822eSBjorn Andersson 140353e2822eSBjorn Andersson return ret; 140453e2822eSBjorn Andersson } 140553e2822eSBjorn Andersson 140653e2822eSBjorn Andersson static const struct of_device_id qcom_smd_of_match[] = { 140753e2822eSBjorn Andersson { .compatible = "qcom,smd" }, 140853e2822eSBjorn Andersson {} 140953e2822eSBjorn Andersson }; 141053e2822eSBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_smd_of_match); 141153e2822eSBjorn Andersson 141253e2822eSBjorn Andersson static struct platform_driver qcom_smd_driver = { 141353e2822eSBjorn Andersson .probe = qcom_smd_probe, 141453e2822eSBjorn Andersson .remove = qcom_smd_remove, 141553e2822eSBjorn Andersson .driver = { 141653e2822eSBjorn Andersson .name = "qcom-smd", 141753e2822eSBjorn Andersson .of_match_table = qcom_smd_of_match, 141853e2822eSBjorn Andersson }, 141953e2822eSBjorn Andersson }; 142053e2822eSBjorn Andersson 142153e2822eSBjorn Andersson static int __init qcom_smd_init(void) 142253e2822eSBjorn Andersson { 142353e2822eSBjorn Andersson return platform_driver_register(&qcom_smd_driver); 142453e2822eSBjorn Andersson } 142553e2822eSBjorn Andersson subsys_initcall(qcom_smd_init); 142653e2822eSBjorn Andersson 142753e2822eSBjorn Andersson static void __exit qcom_smd_exit(void) 142853e2822eSBjorn Andersson { 142953e2822eSBjorn Andersson platform_driver_unregister(&qcom_smd_driver); 143053e2822eSBjorn Andersson } 143153e2822eSBjorn Andersson module_exit(qcom_smd_exit); 143253e2822eSBjorn Andersson 143353e2822eSBjorn Andersson MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 143453e2822eSBjorn Andersson MODULE_DESCRIPTION("Qualcomm Shared Memory Driver"); 143553e2822eSBjorn Andersson MODULE_LICENSE("GPL v2"); 1436