summaryrefslogtreecommitdiff
path: root/drivers/sipc/smsg.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/sipc/smsg.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/sipc/smsg.c')
-rw-r--r--drivers/sipc/smsg.c526
1 files changed, 526 insertions, 0 deletions
diff --git a/drivers/sipc/smsg.c b/drivers/sipc/smsg.c
new file mode 100644
index 00000000..437189dc
--- /dev/null
+++ b/drivers/sipc/smsg.c
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#ifdef CONFIG_SPRD_MAILBOX
+#include <soc/sprd/mailbox.h>
+#endif
+
+#include <linux/sipc.h>
+#include <linux/sipc_priv.h>
+#include <linux/wakelock.h>
+#include <linux/syscore_ops.h>
+
+static struct smsg_ipc *smsg_ipcs[SIPC_ID_NR];
+
+static ushort debug_enable = 0;
+
+static struct wake_lock sipc_wake_lock;
+
+module_param_named(debug_enable, debug_enable, ushort, 0644);
+
+irqreturn_t smsg_irq_handler(int irq, void *dev_id)
+{
+ struct smsg_ipc *ipc = (struct smsg_ipc *)dev_id;
+ struct smsg *msg;
+ struct smsg_channel *ch;
+ uintptr_t rxpos;
+ uint32_t wr;
+
+ if (ipc->rxirq_status()) {
+ ipc->rxirq_clear();
+ }
+
+ while (readl(ipc->rxbuf_wrptr) != readl(ipc->rxbuf_rdptr)) {
+ rxpos = (readl(ipc->rxbuf_rdptr) & (ipc->rxbuf_size - 1)) *
+ sizeof (struct smsg) + ipc->rxbuf_addr;
+ msg = (struct smsg *)rxpos;
+
+ pr_debug("irq get smsg: wrptr=%u, rdptr=%u, rxpos=0x%lx\n",
+ readl(ipc->rxbuf_wrptr), readl(ipc->rxbuf_rdptr), rxpos);
+ pr_debug("irq read smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+ if(msg->type == SMSG_TYPE_DIE) {
+ if(debug_enable)
+ panic("cpcrash");
+ else {
+ /* update smsg rdptr */
+ writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
+
+ continue;
+ }
+ }
+ if (msg->channel >= SMSG_CH_NR || msg->type >= SMSG_TYPE_NR) {
+ /* invalid msg */
+ printk(KERN_ERR "invalid smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+
+ /* update smsg rdptr */
+ writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
+
+ continue;
+ }
+
+ ch = ipc->channels[msg->channel];
+ if (!ch) {
+ if (ipc->states[msg->channel] == CHAN_STATE_UNUSED &&
+ msg->type == SMSG_TYPE_OPEN &&
+ msg->flag == SMSG_OPEN_MAGIC) {
+
+ ipc->states[msg->channel] = CHAN_STATE_WAITING;
+ } else {
+ /* drop this bad msg since channel is not opened */
+ printk(KERN_ERR "smsg channel %d not opened! "
+ "drop smsg: type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+ }
+ /* update smsg rdptr */
+ writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
+
+ continue;
+ }
+
+ atomic_inc(&(ipc->busy[msg->channel]));
+
+ if ((int)(readl(ch->wrptr) - readl(ch->rdptr)) >= SMSG_CACHE_NR) {
+ /* msg cache is full, drop this msg */
+ printk(KERN_ERR "smsg channel %d recv cache is full! "
+ "drop smsg: type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+ } else {
+ /* write smsg to cache */
+ wr = readl(ch->wrptr) & (SMSG_CACHE_NR - 1);
+ memcpy(&(ch->caches[wr]), msg, sizeof(struct smsg));
+ writel(readl(ch->wrptr) + 1, ch->wrptr);
+ }
+
+ /* update smsg rdptr */
+ writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
+
+ wake_up_interruptible_all(&(ch->rxwait));
+
+ atomic_dec(&(ipc->busy[msg->channel]));
+ }
+
+ wake_lock_timeout(&sipc_wake_lock, HZ / 2);
+
+ return IRQ_HANDLED;
+}
+
+int smsg_ipc_create(uint8_t dst, struct smsg_ipc *ipc)
+{
+ int rval;
+
+ if (!ipc->irq_handler) {
+ ipc->irq_handler = smsg_irq_handler;
+ }
+
+ spin_lock_init(&(ipc->txpinlock));
+
+ smsg_ipcs[dst] = ipc;
+
+#ifdef CONFIG_SPRD_MAILBOX
+ /* explicitly call irq handler in case of missing irq on boot */
+ ipc->irq_handler(ipc->core_id, ipc);
+
+ rval = mbox_register_irq_handle(ipc->core_id, ipc->irq_handler, ipc);
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to register irq handler in mailbox: %s\n",
+ ipc->name);
+ return rval;
+ }
+#else
+ /* explicitly call irq handler in case of missing irq on boot */
+ ipc->irq_handler(ipc->irq, ipc);
+
+ /* register IPI irq */
+ rval = request_irq(ipc->irq, ipc->irq_handler,
+ IRQF_NO_SUSPEND, ipc->name, ipc);
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to request irq %s: %d\n",
+ ipc->name, ipc->irq);
+ return rval;
+ }
+#endif
+ return 0;
+}
+
+int smsg_ipc_destroy(uint8_t dst)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+
+ kthread_stop(ipc->thread);
+ free_irq(ipc->irq, ipc);
+ smsg_ipcs[dst] = NULL;
+
+ return 0;
+}
+
+/* ****************************************************************** */
+
+int smsg_ch_open(uint8_t dst, uint8_t channel, int timeout)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+ struct smsg_channel *ch;
+ struct smsg mopen, mrecv;
+ int rval = 0;
+
+ if(!ipc) {
+ return -ENODEV;
+ }
+
+ ch = kzalloc(sizeof(struct smsg_channel), GFP_KERNEL);
+ if (!ch) {
+ return -ENOMEM;
+ }
+
+ atomic_set(&(ipc->busy[channel]), 1);
+ init_waitqueue_head(&(ch->rxwait));
+ mutex_init(&(ch->rxlock));
+ ipc->channels[channel] = ch;
+
+ smsg_set(&mopen, channel, SMSG_TYPE_OPEN, SMSG_OPEN_MAGIC, 0);
+ rval = smsg_send(dst, &mopen, timeout);
+ if (rval != 0) {
+ printk(KERN_ERR "smsg_ch_open smsg send error, errno %d!\n", rval);
+ ipc->states[channel] = CHAN_STATE_UNUSED;
+ ipc->channels[channel] = NULL;
+ atomic_dec(&(ipc->busy[channel]));
+ /* guarantee that channel resource isn't used in irq handler */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+ kfree(ch);
+
+ return rval;
+ }
+
+ /* open msg might be got before */
+ if (ipc->states[channel] == CHAN_STATE_WAITING) {
+ goto open_done;
+ }
+
+ smsg_set(&mrecv, channel, 0, 0, 0);
+ rval = smsg_recv(dst, &mrecv, timeout);
+ if (rval != 0) {
+ printk(KERN_ERR "smsg_ch_open smsg receive error, errno %d!\n", rval);
+ ipc->states[channel] = CHAN_STATE_UNUSED;
+ ipc->channels[channel] = NULL;
+ atomic_dec(&(ipc->busy[channel]));
+ /* guarantee that channel resource isn't used in irq handler */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+ kfree(ch);
+
+ return rval;
+ }
+
+ if (mrecv.type != SMSG_TYPE_OPEN || mrecv.flag != SMSG_OPEN_MAGIC) {
+ printk(KERN_ERR "Got bad open msg on channel %d-%d\n", dst, channel);
+ ipc->states[channel] = CHAN_STATE_UNUSED;
+ ipc->channels[channel] = NULL;
+ atomic_dec(&(ipc->busy[channel]));
+ /* guarantee that channel resource isn't used in irq handler */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+ kfree(ch);
+
+ return -EIO;
+ }
+
+open_done:
+ ipc->states[channel] = CHAN_STATE_OPENED;
+ atomic_dec(&(ipc->busy[channel]));
+
+ return 0;
+}
+
+int smsg_ch_close(uint8_t dst, uint8_t channel, int timeout)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+ struct smsg_channel *ch = ipc->channels[channel];
+ struct smsg mclose;
+
+ if (!ch) {
+ return 0;
+ }
+
+ smsg_set(&mclose, channel, SMSG_TYPE_CLOSE, SMSG_CLOSE_MAGIC, 0);
+ smsg_send(dst, &mclose, timeout);
+
+ ipc->states[channel] = CHAN_STATE_FREE;
+ wake_up_interruptible_all(&(ch->rxwait));
+
+ /* wait for the channel beeing unused */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+
+ /* maybe channel has been free for smsg_ch_open failed */
+ if (ipc->channels[channel]){
+ ipc->channels[channel] = NULL;
+ /* guarantee that channel resource isn't used in irq handler */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+ kfree(ch);
+ }
+
+ /* finally, update the channel state*/
+ ipc->states[channel] = CHAN_STATE_UNUSED;
+
+ return 0;
+}
+
+int smsg_send(uint8_t dst, struct smsg *msg, int timeout)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+ uintptr_t txpos;
+ int rval = 0;
+ unsigned long flags;
+
+ if(!ipc) {
+ return -ENODEV;
+ }
+
+ if (!ipc->channels[msg->channel]) {
+ printk(KERN_ERR "channel %d not inited!\n", msg->channel);
+ return -ENODEV;
+ }
+
+ if (ipc->states[msg->channel] != CHAN_STATE_OPENED &&
+ msg->type != SMSG_TYPE_OPEN &&
+ msg->type != SMSG_TYPE_CLOSE) {
+ printk(KERN_ERR "channel %d not opened!\n", msg->channel);
+ return -EINVAL;
+ }
+
+ pr_debug("smsg_send: dst=%d, channel=%d, timeout=%d\n",
+ dst, msg->channel, timeout);
+ pr_debug("send smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+
+ spin_lock_irqsave(&(ipc->txpinlock), flags);
+ if ((int)(readl(ipc->txbuf_wrptr) -
+ readl(ipc->txbuf_rdptr)) >= ipc->txbuf_size) {
+ printk(KERN_WARNING "smsg txbuf is full!\n");
+ rval = -EBUSY;
+ goto send_failed;
+ }
+
+ /* calc txpos and write smsg */
+ txpos = (readl(ipc->txbuf_wrptr) & (ipc->txbuf_size - 1)) *
+ sizeof(struct smsg) + ipc->txbuf_addr;
+ memcpy((void *)txpos, msg, sizeof(struct smsg));
+
+ pr_debug("write smsg: wrptr=%u, rdptr=%u, txpos=0x%lx\n",
+ readl(ipc->txbuf_wrptr),
+ readl(ipc->txbuf_rdptr), txpos);
+
+ /* update wrptr */
+ writel(readl(ipc->txbuf_wrptr) + 1, ipc->txbuf_wrptr);
+ ipc->txirq_trigger();
+
+send_failed:
+ spin_unlock_irqrestore(&(ipc->txpinlock), flags);
+
+ return rval;
+}
+
+int smsg_recv(uint8_t dst, struct smsg *msg, int timeout)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+ struct smsg_channel *ch;
+ uint32_t rd;
+ int rval = 0;
+
+ if(!ipc) {
+ return -ENODEV;
+ }
+
+ atomic_inc(&(ipc->busy[msg->channel]));
+
+ ch = ipc->channels[msg->channel];
+
+ if (!ch) {
+ printk(KERN_ERR "channel %d not opened!\n", msg->channel);
+ atomic_dec(&(ipc->busy[msg->channel]));
+ return -ENODEV;
+ }
+
+ pr_debug("smsg_recv: dst=%d, channel=%d, timeout=%d\n",
+ dst, msg->channel, timeout);
+
+
+ if (timeout == 0) {
+ if (!mutex_trylock(&(ch->rxlock))) {
+ printk(KERN_INFO "recv smsg busy!\n");
+ atomic_dec(&(ipc->busy[msg->channel]));
+
+ return -EBUSY;
+ }
+
+ /* no wait */
+ if (readl(ch->wrptr) == readl(ch->rdptr)) {
+ printk(KERN_WARNING "smsg rx cache is empty!\n");
+ rval = -ENODATA;
+
+ goto recv_failed;
+ }
+ } else if (timeout < 0) {
+ mutex_lock(&(ch->rxlock));
+ /* wait forever */
+ rval = wait_event_interruptible(ch->rxwait,
+ (readl(ch->wrptr) != readl(ch->rdptr)) ||
+ (ipc->states[msg->channel] == CHAN_STATE_FREE));
+ if (rval < 0) {
+ printk(KERN_WARNING "smsg_recv wait interrupted!\n");
+
+ goto recv_failed;
+ }
+
+ if (ipc->states[msg->channel] == CHAN_STATE_FREE) {
+ printk(KERN_WARNING "smsg_recv smsg channel is free!\n");
+ rval = -EIO;
+
+ goto recv_failed;
+ }
+ } else {
+ mutex_lock(&(ch->rxlock));
+ /* wait timeout */
+ rval = wait_event_interruptible_timeout(ch->rxwait,
+ (readl(ch->wrptr) != readl(ch->rdptr)) ||
+ (ipc->states[msg->channel] == CHAN_STATE_FREE), timeout);
+ if (rval < 0) {
+ printk(KERN_WARNING "smsg_recv wait interrupted!\n");
+
+ goto recv_failed;
+ } else if (rval == 0) {
+ printk(KERN_WARNING "smsg_recv wait timeout!\n");
+ rval = -ETIME;
+
+ goto recv_failed;
+ }
+
+ if (ipc->states[msg->channel] == CHAN_STATE_FREE) {
+ printk(KERN_ERR "smsg_recv smsg channel is free!\n");
+ rval = -EIO;
+
+ goto recv_failed;
+ }
+ }
+
+ /* read smsg from cache */
+ rd = readl(ch->rdptr) & (SMSG_CACHE_NR - 1);
+ memcpy(msg, &(ch->caches[rd]), sizeof(struct smsg));
+ writel(readl(ch->rdptr) + 1, ch->rdptr);
+
+ pr_debug("read smsg: wrptr=%d, rdptr=%d, rd=%d\n",
+ readl(ch->wrptr), readl(ch->rdptr), rd);
+ pr_debug("recv smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+
+recv_failed:
+ mutex_unlock(&(ch->rxlock));
+ atomic_dec(&(ipc->busy[msg->channel]));
+ return rval;
+}
+
+
+#if defined(CONFIG_DEBUG_FS)
+static int smsg_debug_show(struct seq_file *m, void *private)
+{
+ struct smsg_ipc *smsg_sipc = NULL;
+ int i, j;
+
+ for (i = 0; i < SIPC_ID_NR; i++) {
+ smsg_sipc = smsg_ipcs[i];
+ if (!smsg_sipc) {
+ continue;
+ }
+ seq_printf(m, "sipc: %s: \n", smsg_sipc->name);
+ seq_printf(m, "dst: 0x%0x, irq: 0x%0x\n",
+ smsg_sipc->dst, smsg_sipc->irq);
+ seq_printf(m, "txbufAddr: 0x%0x, txbufsize: 0x%0x, txbufrdptr: [0x%0x]=%lu, txbufwrptr: [0x%0x]=%lu\n",
+ smsg_sipc->txbuf_addr, smsg_sipc->txbuf_size, smsg_sipc->txbuf_rdptr, readl(smsg_sipc->txbuf_rdptr), smsg_sipc->txbuf_wrptr, readl(smsg_sipc->txbuf_wrptr));
+ seq_printf(m, "rxbufAddr: 0x%0x, rxbufsize: 0x%0x, rxbufrdptr: [0x%0x]=%lu, rxbufwrptr: [0x%0x]=%lu\n",
+ smsg_sipc->rxbuf_addr, smsg_sipc->rxbuf_size, smsg_sipc->rxbuf_rdptr, readl(smsg_sipc->rxbuf_rdptr), smsg_sipc->rxbuf_wrptr, readl(smsg_sipc->rxbuf_wrptr));
+
+ for (j=0; j<SMSG_CH_NR; j++) {
+ seq_printf(m, "channel[%d] states: %d\n", j, smsg_sipc->states[j]);
+ }
+ }
+ return 0;
+}
+
+static int smsg_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, smsg_debug_show, inode->i_private);
+}
+
+static const struct file_operations smsg_debug_fops = {
+ .open = smsg_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int smsg_init_debugfs(void *root )
+{
+ if (!root)
+ return -ENXIO;
+ debugfs_create_file("smsg", S_IRUGO, (struct dentry *)root, NULL, &smsg_debug_fops);
+ return 0;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int sipc_syscore_suspend(void)
+{
+ int ret = has_wake_lock(WAKE_LOCK_SUSPEND) ? -EAGAIN : 0;
+ return ret;
+}
+
+static struct syscore_ops sipc_syscore_ops = {
+ .suspend = sipc_syscore_suspend,
+};
+
+int smsg_suspend_init(void)
+{
+ wake_lock_init(&sipc_wake_lock, WAKE_LOCK_SUSPEND, "sipc-smsg");
+ register_syscore_ops(&sipc_syscore_ops);
+
+ return 0;
+}
+
+
+EXPORT_SYMBOL(smsg_ch_open);
+EXPORT_SYMBOL(smsg_ch_close);
+EXPORT_SYMBOL(smsg_send);
+EXPORT_SYMBOL(smsg_recv);
+
+MODULE_AUTHOR("Chen Gaopeng");
+MODULE_DESCRIPTION("SIPC/SMSG driver");
+MODULE_LICENSE("GPL");