1104 lines
26 KiB
C
Executable File
1104 lines
26 KiB
C
Executable File
/*
|
|
* Copyright (C) 2019 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/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/sched.h>
|
|
|
|
#ifdef CONFIG_SPRD_MAILBOX
|
|
#include <linux/sprd_mailbox.h>
|
|
#endif
|
|
|
|
#include "../include/sipc.h"
|
|
#include "sipc_priv.h"
|
|
#include "../include/sprd_pcie_resource.h"
|
|
|
|
#if defined(CONFIG_DEBUG_FS)
|
|
#include "sipc_debugfs.h"
|
|
#endif
|
|
|
|
#define SMSG_TXBUF_ADDR (0)
|
|
#define SMSG_TXBUF_SIZE (SZ_1K)
|
|
#define SMSG_RXBUF_ADDR (SMSG_TXBUF_SIZE)
|
|
#define SMSG_RXBUF_SIZE (SZ_1K)
|
|
|
|
#define SMSG_RINGHDR (SMSG_TXBUF_SIZE + SMSG_RXBUF_SIZE)
|
|
#define SMSG_TXBUF_RDPTR (SMSG_RINGHDR + 0)
|
|
#define SMSG_TXBUF_WRPTR (SMSG_RINGHDR + 4)
|
|
#define SMSG_RXBUF_RDPTR (SMSG_RINGHDR + 8)
|
|
#define SMSG_RXBUF_WRPTR (SMSG_RINGHDR + 12)
|
|
|
|
#define SMSG_RESERVE_BASE (SMSG_RINGHDR + SZ_1K)
|
|
#define SMSG_PCIE_WRPTR (SMSG_RESERVE_BASE + 0)
|
|
#define SMSG_PCIE_IRQPTR (SMSG_RESERVE_BASE + 4)
|
|
|
|
#define SIPC_READL(addr) readl((__force void __iomem *)(addr))
|
|
#define SIPC_WRITEL(b, addr) writel(b, (__force void __iomem *)(addr))
|
|
|
|
static u8 g_wakeup_flag;
|
|
|
|
struct smsg_ipc *smsg_ipcs[SIPC_ID_NR];
|
|
EXPORT_SYMBOL_GPL(smsg_ipcs);
|
|
|
|
static ushort debug_enable;
|
|
|
|
module_param_named(debug_enable, debug_enable, ushort, 0644);
|
|
static u8 channel2index[SMSG_CH_NR + 1];
|
|
|
|
static int smsg_ipc_smem_init(struct smsg_ipc *ipc);
|
|
|
|
void smsg_init_channel2index(void)
|
|
{
|
|
u16 i, j;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(channel2index); i++) {
|
|
for (j = 0; j < SMSG_VALID_CH_NR; j++) {
|
|
/* find the index of channel i */
|
|
if (sipc_cfg[j].channel == i)
|
|
break;
|
|
}
|
|
|
|
/* if not find, init with INVALID_CHANEL_INDEX,
|
|
* else init whith j
|
|
*/
|
|
if (j == SMSG_VALID_CH_NR)
|
|
channel2index[i] = INVALID_CHANEL_INDEX;
|
|
else
|
|
channel2index[i] = j;
|
|
}
|
|
}
|
|
|
|
static void get_channel_status(u8 dst, char *status, int size)
|
|
{
|
|
int i, len;
|
|
struct smsg_channel *ch;
|
|
|
|
len = strlen(status);
|
|
for (i = 0; i < SMSG_VALID_CH_NR && len < size; i++) {
|
|
ch = smsg_ipcs[dst]->channels[i];
|
|
if (!ch)
|
|
continue;
|
|
if (SIPC_READL(ch->rdptr) < SIPC_READL(ch->wrptr))
|
|
snprintf(
|
|
status + len,
|
|
size - len,
|
|
"dst-%d-ch-%d: rd = %u, wr = %u.\n",
|
|
dst,
|
|
i,
|
|
SIPC_READL(ch->rdptr),
|
|
SIPC_READL(ch->wrptr)
|
|
);
|
|
}
|
|
}
|
|
|
|
static void smsg_wakeup_print(struct smsg_ipc *ipc, struct smsg *msg)
|
|
{
|
|
/* if the first msg come after the irq wake up by sipc,
|
|
* use prin_fo to output log
|
|
*/
|
|
if (g_wakeup_flag) {
|
|
g_wakeup_flag = 0;
|
|
pr_info("irq read smsg: dst=%d, channel=%d,type=%d, flag=0x%04x, value=0x%08x\n",
|
|
ipc->dst,
|
|
msg->channel,
|
|
msg->type,
|
|
msg->flag,
|
|
msg->value);
|
|
} else {
|
|
pr_debug("irq read smsg: dst=%d, channel=%d,type=%d, flag=0x%04x, value=0x%08x\n",
|
|
ipc->dst,
|
|
msg->channel,
|
|
msg->type,
|
|
msg->flag,
|
|
msg->value);
|
|
}
|
|
}
|
|
|
|
static void smsg_die_process(struct smsg_ipc *ipc, struct smsg *msg)
|
|
{
|
|
if (msg->type == SMSG_TYPE_DIE) {
|
|
if (debug_enable) {
|
|
char sipc_status[100] = {0};
|
|
|
|
get_channel_status(ipc->dst,
|
|
sipc_status,
|
|
sizeof(sipc_status));
|
|
sbuf_get_status(ipc->dst,
|
|
sipc_status,
|
|
sizeof(sipc_status));
|
|
panic("cpcrash: %s", sipc_status);
|
|
while (1)
|
|
;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void smsg_msg_process(struct smsg_ipc *ipc,
|
|
struct smsg *msg, bool wake_lock)
|
|
{
|
|
struct smsg_channel *ch = NULL;
|
|
u32 wr;
|
|
u8 ch_index;
|
|
|
|
smsg_wakeup_print(ipc, msg);
|
|
smsg_die_process(ipc, msg);
|
|
|
|
ch_index = channel2index[msg->channel];
|
|
atomic_inc(&ipc->busy[ch_index]);
|
|
|
|
pr_debug("smsg:get dst=%d msg channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
|
|
ipc->dst, msg->channel,
|
|
msg->type, msg->flag,
|
|
msg->value);
|
|
|
|
if (msg->type >= SMSG_TYPE_NR) {
|
|
/* invalid msg */
|
|
pr_err("invalid smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
|
|
msg->channel, msg->type, msg->flag, msg->value);
|
|
goto exit_msg_proc;
|
|
}
|
|
|
|
ch = ipc->channels[ch_index];
|
|
if (!ch) {
|
|
if (ipc->states[ch_index] == CHAN_STATE_UNUSED &&
|
|
msg->type == SMSG_TYPE_OPEN &&
|
|
msg->flag == SMSG_OPEN_MAGIC)
|
|
ipc->states[ch_index] = CHAN_STATE_CLIENT_OPENED;
|
|
else
|
|
/* drop this bad msg since channel
|
|
* is not opened
|
|
*/
|
|
pr_info("smsg channel %d not opened! drop smsg: type=%d, flag=0x%04x, value=0x%08x\n",
|
|
msg->channel, msg->type,
|
|
msg->flag, msg->value);
|
|
|
|
goto exit_msg_proc;
|
|
}
|
|
|
|
if ((int)(SIPC_READL(ch->wrptr) - SIPC_READL(ch->rdptr)) >=
|
|
SMSG_CACHE_NR) {
|
|
/* msg cache is full, drop this msg */
|
|
pr_info("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 = SIPC_READL(ch->wrptr) & (SMSG_CACHE_NR - 1);
|
|
memcpy(&ch->caches[wr], msg, sizeof(struct smsg));
|
|
SIPC_WRITEL(SIPC_READL(ch->wrptr) + 1, ch->wrptr);
|
|
}
|
|
|
|
wake_up_interruptible_all(&ch->rxwait);
|
|
|
|
if (wake_lock)
|
|
sprd_pms_request_wakelock_period(ch->rx_pms, 500);
|
|
|
|
exit_msg_proc:
|
|
atomic_dec(&ipc->busy[ch_index]);
|
|
}
|
|
|
|
#ifdef CONFIG_SPRD_MAILBOX
|
|
static irqreturn_t smsg_mbox_irq_handler(void *ptr, void *private)
|
|
{
|
|
struct smsg_ipc *ipc = (struct smsg_ipc *)private;
|
|
struct smsg *msg;
|
|
|
|
msg = ptr;
|
|
smsg_msg_process(ipc, msg, true);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t smsg_mbox_sensor_irq_handler(void *ptr, void *private)
|
|
{
|
|
struct smsg_ipc *ipc = (struct smsg_ipc *)private;
|
|
struct smsg *msg;
|
|
|
|
msg = ptr;
|
|
smsg_msg_process(ipc, msg, false);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int sipc_process_all_msg(struct smsg_ipc *ipc)
|
|
{
|
|
struct smsg *msg;
|
|
struct smsg msg_recv;
|
|
uintptr_t rxpos;
|
|
|
|
/* msg coming, means resource ok, don't wait */
|
|
sipc_smem_request_resource(ipc->sipc_pms, ipc->dst, 0);
|
|
|
|
while (BL_READL(ipc->rxbuf_wrptr) != BL_READL(ipc->rxbuf_rdptr)) {
|
|
rxpos = (BL_READL(ipc->rxbuf_rdptr) & (ipc->rxbuf_size - 1)) *
|
|
sizeof(struct smsg) + ipc->rxbuf_addr;
|
|
msg = (struct smsg *)rxpos;
|
|
|
|
/* share memory smsg to ipc msg */
|
|
msg_recv.channel = msg->channel;
|
|
msg_recv.type = msg->type;
|
|
msg_recv.flag = BL_GETW(msg->flag);
|
|
msg_recv.value= BL_GETL(msg->value);
|
|
smsg_msg_process(ipc, &msg_recv, true);
|
|
/* update smsg rdptr */
|
|
BL_WRITEL(BL_READL(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
|
|
}
|
|
|
|
sipc_smem_release_resource(ipc->sipc_pms, ipc->dst);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static irqreturn_t smsg_irq_handler(int irq, void *private)
|
|
{
|
|
struct smsg_ipc *ipc = (struct smsg_ipc *)private;
|
|
|
|
if (ipc->rxirq_status(ipc->dst))
|
|
ipc->rxirq_clear(ipc->dst);
|
|
|
|
sipc_process_all_msg(ipc);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void smsg_ipc_init_smsg_irq_callback(struct smsg_ipc *ipc)
|
|
{
|
|
#ifdef CONFIG_SPRD_MAILBOX
|
|
if (ipc->type == SIPC_BASE_MBOX) {
|
|
mbox_register_irq_handle(ipc->core_id,
|
|
smsg_mbox_irq_handler, ipc);
|
|
|
|
if ((ipc->dst == SIPC_ID_PM_SYS) &&
|
|
(ipc->core_sensor_id != MBOX_INVALID_CORE))
|
|
mbox_register_irq_handle(ipc->core_sensor_id,
|
|
smsg_mbox_sensor_irq_handler,
|
|
ipc);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_SPRD_PCIE_EP_DEVICE
|
|
if (ipc->type == SIPC_BASE_PCIE) {
|
|
sprd_ep_dev_register_irq_handler(ipc->ep_dev,
|
|
PCIE_MSI_SIPC_IRQ,
|
|
smsg_irq_handler, ipc);
|
|
sprd_ep_dev_set_irq_addr(ipc->ep_dev, ipc->write_addr + 4);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PCIE_EPF_SPRD
|
|
if (ipc->type == SIPC_BASE_PCIE) {
|
|
sprd_pci_epf_register_irq_handler(ipc->ep_fun,
|
|
PCIE_DBELL_SIPC_IRQ,
|
|
smsg_irq_handler,
|
|
ipc);
|
|
sprd_pci_epf_set_write_addr(ipc->ep_fun, ipc->write_addr);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (ipc->type == SIPC_BASE_IPI) {
|
|
int ret;
|
|
|
|
/* explicitly call irq handler in case of missing irq on boot */
|
|
smsg_irq_handler(ipc->irq, ipc);
|
|
|
|
/* register IPI irq */
|
|
ret = request_irq(ipc->irq,
|
|
smsg_irq_handler,
|
|
IRQF_NO_SUSPEND,
|
|
ipc->name,
|
|
ipc);
|
|
if (ret)
|
|
pr_info("%s: request irq err = %d!\n", ipc->name, ret);
|
|
}
|
|
}
|
|
|
|
static int smsg_ipc_smem_init(struct smsg_ipc *ipc)
|
|
{
|
|
void __iomem *base, *p;
|
|
phys_addr_t offset = 0;
|
|
int ret;
|
|
|
|
pr_debug("%s: %s, smem_type = %d!\n",
|
|
__func__, ipc->name, ipc->smem_type);
|
|
|
|
ret = smem_init(ipc->smem_base, ipc->smem_size,
|
|
ipc->dst, ipc->smem_type);
|
|
if (ret) {
|
|
pr_err("%s: %s err = %d!\n", __func__, ipc->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (ipc->type != SIPC_BASE_MBOX) {
|
|
ipc->ring_base = smem_alloc(ipc->dst, SZ_4K);
|
|
ipc->ring_size = SZ_4K;
|
|
pr_info("%s: ring_base = 0x%x, ring_size = 0x%x\n",
|
|
__func__,
|
|
ipc->ring_base,
|
|
ipc->ring_size);
|
|
}
|
|
|
|
#ifdef CONFIG_PHYS_ADDR_T_64BIT
|
|
offset = ipc->high_offset;
|
|
offset = offset << 32;
|
|
#endif
|
|
|
|
if (ipc->ring_base) {
|
|
base = (void __iomem *)shmem_ram_vmap_nocache(ipc->dst,
|
|
ipc->ring_base + offset,
|
|
ipc->ring_size);
|
|
if (!base) {
|
|
pr_err("%s: ioremap failed!\n", __func__);
|
|
smem_free(ipc->dst, ipc->ring_base, SZ_4K);
|
|
ipc->ring_base = 0;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* assume client is boot later than host */
|
|
if (!ipc->client) {
|
|
/**
|
|
* memset(base, 0, ipc->ring_size);
|
|
* the instruction dc avz
|
|
* will abort for nocache memory
|
|
*/
|
|
for (p = base; p < base + ipc->ring_size;) {
|
|
#ifdef CONFIG_64BIT
|
|
*(uint64_t *)p = 0x0;
|
|
p += sizeof(uint64_t);
|
|
#else
|
|
*(u32 *)p = 0x0;
|
|
p += sizeof(u32);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (ipc->client) {
|
|
/* clent mode, tx is host rx , rx is host tx*/
|
|
ipc->smem_vbase = (void *)base;
|
|
ipc->txbuf_size = SMSG_RXBUF_SIZE /
|
|
sizeof(struct smsg);
|
|
ipc->txbuf_addr = (uintptr_t)base +
|
|
SMSG_RXBUF_ADDR;
|
|
ipc->txbuf_rdptr = (uintptr_t)base +
|
|
SMSG_RXBUF_RDPTR;
|
|
ipc->txbuf_wrptr = (uintptr_t)base +
|
|
SMSG_RXBUF_WRPTR;
|
|
ipc->rxbuf_size = SMSG_TXBUF_SIZE /
|
|
sizeof(struct smsg);
|
|
ipc->rxbuf_addr = (uintptr_t)base +
|
|
SMSG_TXBUF_ADDR;
|
|
ipc->rxbuf_rdptr = (uintptr_t)base +
|
|
SMSG_TXBUF_RDPTR;
|
|
ipc->rxbuf_wrptr = (uintptr_t)base +
|
|
SMSG_TXBUF_WRPTR;
|
|
} else {
|
|
ipc->smem_vbase = (void *)base;
|
|
ipc->txbuf_size = SMSG_TXBUF_SIZE /
|
|
sizeof(struct smsg);
|
|
ipc->txbuf_addr = (uintptr_t)base +
|
|
SMSG_TXBUF_ADDR;
|
|
ipc->txbuf_rdptr = (uintptr_t)base +
|
|
SMSG_TXBUF_RDPTR;
|
|
ipc->txbuf_wrptr = (uintptr_t)base +
|
|
SMSG_TXBUF_WRPTR;
|
|
ipc->rxbuf_size = SMSG_RXBUF_SIZE /
|
|
sizeof(struct smsg);
|
|
ipc->rxbuf_addr = (uintptr_t)base +
|
|
SMSG_RXBUF_ADDR;
|
|
ipc->rxbuf_rdptr = (uintptr_t)base +
|
|
SMSG_RXBUF_RDPTR;
|
|
ipc->rxbuf_wrptr = (uintptr_t)base +
|
|
SMSG_RXBUF_WRPTR;
|
|
}
|
|
ipc->write_addr = base + SMSG_PCIE_WRPTR;
|
|
}
|
|
|
|
/* after smem_init complete, regist msg irq */
|
|
smsg_ipc_init_smsg_irq_callback(ipc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PCIE_EPF_SPRD
|
|
static void smsg_pcie_first_ready(void *data)
|
|
{
|
|
struct smsg_ipc *ipc = (struct smsg_ipc *)data;
|
|
|
|
if (ipc->smem_type == SMEM_PCIE)
|
|
smsg_ipc_smem_init(ipc);
|
|
else
|
|
pr_err("%s: pcie first ready, smem_type =%d!\n",
|
|
ipc->name, ipc->smem_type);
|
|
}
|
|
#endif
|
|
|
|
static void smsg_ipc_mpm_init(struct smsg_ipc *ipc)
|
|
{
|
|
/* create modem power manger instance for this sipc */
|
|
sprd_mpm_create(ipc->dst, ipc->name, ipc->latency);
|
|
|
|
/* init a power manager source */
|
|
ipc->sipc_pms = sprd_pms_create(ipc->dst, ipc->name, true);
|
|
if (!ipc->sipc_pms)
|
|
pr_warn("create pms %s failed!\n", ipc->name);
|
|
|
|
if (ipc->type == SIPC_BASE_PCIE) {
|
|
/* int mpm resource ops */
|
|
sprd_mpm_init_resource_ops(ipc->dst,
|
|
sprd_pcie_wait_resource,
|
|
sprd_pcie_request_resource,
|
|
sprd_pcie_release_resource);
|
|
|
|
#ifdef CONFIG_SPRD_PCIE_EP_DEVICE
|
|
/* in pcie host side, init pcie host resource */
|
|
sprd_pcie_resource_host_init(ipc->dst,
|
|
ipc->ep_dev, ipc->pcie_dev);
|
|
#endif
|
|
|
|
#ifdef CONFIG_PCIE_EPF_SPRD
|
|
/* in pcie ep side, init pcie client resource */
|
|
sprd_pcie_resource_client_init(ipc->dst, ipc->ep_fun);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void smsg_ipc_create(struct smsg_ipc *ipc)
|
|
{
|
|
pr_info("%s: %s\n", __func__, ipc->name);
|
|
|
|
smsg_ipcs[ipc->dst] = ipc;
|
|
|
|
smsg_ipc_mpm_init(ipc);
|
|
|
|
|
|
if (ipc->type == SIPC_BASE_PCIE) {
|
|
#ifdef CONFIG_PCIE_EPF_SPRD
|
|
/* set epf door bell irq number */
|
|
sprd_pci_epf_set_irq_number(ipc->ep_fun, ipc->irq);
|
|
|
|
/* register first pcie ready notify */
|
|
sprd_register_pcie_resource_first_ready(ipc->dst,
|
|
smsg_pcie_first_ready,
|
|
ipc);
|
|
#endif
|
|
}
|
|
|
|
/* if SMEM_PCIE, must init after pcie ready */
|
|
if (ipc->smem_type != SMEM_PCIE)
|
|
smsg_ipc_smem_init(ipc);
|
|
}
|
|
|
|
void smsg_ipc_destroy(struct smsg_ipc *ipc)
|
|
{
|
|
shmem_ram_unmap(ipc->dst, ipc->smem_vbase);
|
|
smem_free(ipc->dst, ipc->ring_base, SZ_4K);
|
|
|
|
#ifdef CONFIG_SPRD_MAILBOX
|
|
if (ipc->type == SIPC_BASE_MBOX) {
|
|
mbox_unregister_irq_handle(ipc->core_id);
|
|
|
|
if ((ipc->dst == SIPC_ID_PM_SYS) &&
|
|
(ipc->core_sensor_id != MBOX_INVALID_CORE))
|
|
mbox_unregister_irq_handle(ipc->core_sensor_id);
|
|
}
|
|
#endif
|
|
|
|
if (ipc->type == SIPC_BASE_PCIE) {
|
|
#ifdef CONFIG_SPRD_PCIE_EP_DEVICE
|
|
sprd_ep_dev_unregister_irq_handler(ipc->ep_dev, ipc->irq);
|
|
#endif
|
|
|
|
#ifdef CONFIG_PCIE_EPF_SPRD
|
|
sprd_pci_epf_unregister_irq_handler(ipc->ep_fun, ipc->irq);
|
|
#endif
|
|
sprd_pcie_resource_trash(ipc->dst);
|
|
} else {
|
|
free_irq(ipc->irq, ipc);
|
|
}
|
|
|
|
smsg_ipcs[ipc->dst] = NULL;
|
|
}
|
|
|
|
int sipc_get_wakeup_flag(void)
|
|
{
|
|
return (int)g_wakeup_flag;
|
|
}
|
|
EXPORT_SYMBOL_GPL(sipc_get_wakeup_flag);
|
|
|
|
void sipc_set_wakeup_flag(void)
|
|
{
|
|
g_wakeup_flag = 1;
|
|
}
|
|
EXPORT_SYMBOL_GPL(sipc_set_wakeup_flag);
|
|
|
|
void sipc_clear_wakeup_flag(void)
|
|
{
|
|
g_wakeup_flag = 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(sipc_clear_wakeup_flag);
|
|
|
|
int smsg_ch_wake_unlock(u8 dst, u8 channel)
|
|
{
|
|
struct smsg_ipc *ipc = smsg_ipcs[dst];
|
|
struct smsg_channel *ch;
|
|
u8 ch_index;
|
|
|
|
ch_index = channel2index[channel];
|
|
if (ch_index == INVALID_CHANEL_INDEX) {
|
|
pr_err("%s:channel %d invalid!\n", __func__, channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ipc)
|
|
return -ENODEV;
|
|
|
|
ch = ipc->channels[ch_index];
|
|
if (!ch)
|
|
return -ENODEV;
|
|
|
|
sprd_pms_release_wakelock(ch->rx_pms);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smsg_ch_wake_unlock);
|
|
|
|
int smsg_ch_open(u8 dst, u8 channel, int timeout)
|
|
{
|
|
struct smsg_ipc *ipc = smsg_ipcs[dst];
|
|
struct smsg_channel *ch;
|
|
struct smsg mopen;
|
|
struct smsg mrecv;
|
|
int rval = 0;
|
|
u8 ch_index;
|
|
|
|
ch_index = channel2index[channel];
|
|
if (ch_index == INVALID_CHANEL_INDEX) {
|
|
pr_err("%s:channel %d invalid!\n", __func__, channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ipc)
|
|
return -ENODEV;
|
|
|
|
ch = kzalloc(sizeof(*ch), GFP_KERNEL);
|
|
if (!ch)
|
|
return -ENOMEM;
|
|
|
|
sprintf(ch->tx_name, "smsg-%d-%d-tx", dst, channel);
|
|
ch->tx_pms = sprd_pms_create(dst, ch->tx_name, true);
|
|
if (!ch->tx_pms)
|
|
pr_warn("create pms %s failed!\n", ch->tx_name);
|
|
|
|
sprintf(ch->rx_name, "smsg-%d-%d-rx", dst, channel);
|
|
ch->rx_pms = sprd_pms_create(dst, ch->rx_name, true);
|
|
if (!ch->rx_pms)
|
|
pr_warn("create pms %s failed!\n", ch->rx_name);
|
|
|
|
atomic_set(&ipc->busy[ch_index], 1);
|
|
init_waitqueue_head(&ch->rxwait);
|
|
mutex_init(&ch->rxlock);
|
|
ipc->channels[ch_index] = ch;
|
|
|
|
pr_info("%s: channel %d-%d send open msg!\n",
|
|
__func__, dst, channel);
|
|
|
|
smsg_set(&mopen, channel, SMSG_TYPE_OPEN, SMSG_OPEN_MAGIC, 0);
|
|
rval = smsg_send(dst, &mopen, timeout);
|
|
if (rval != 0) {
|
|
pr_err("%s: channel %d-%d send open msg error = %d!\n",
|
|
__func__, dst, channel, rval);
|
|
ipc->states[ch_index] = CHAN_STATE_UNUSED;
|
|
ipc->channels[ch_index] = NULL;
|
|
atomic_dec(&ipc->busy[ch_index]);
|
|
/* guarantee that channel resource isn't used in irq handler */
|
|
while (atomic_read(&ipc->busy[ch_index]))
|
|
;
|
|
|
|
kfree(ch);
|
|
|
|
return rval;
|
|
}
|
|
|
|
/* open msg might be got before */
|
|
if (ipc->states[ch_index] == CHAN_STATE_CLIENT_OPENED)
|
|
goto open_done;
|
|
|
|
ipc->states[ch_index] = CHAN_STATE_HOST_OPENED;
|
|
|
|
do {
|
|
smsg_set(&mrecv, channel, 0, 0, 0);
|
|
rval = smsg_recv(dst, &mrecv, timeout);
|
|
if (rval != 0) {
|
|
pr_err("%s: channel %d-%d smsg receive error = %d!\n",
|
|
__func__, dst, channel, rval);
|
|
ipc->states[ch_index] = CHAN_STATE_UNUSED;
|
|
ipc->channels[ch_index] = NULL;
|
|
atomic_dec(&ipc->busy[ch_index]);
|
|
/* guarantee that channel resource isn't used
|
|
* in irq handler
|
|
*/
|
|
while (atomic_read(&ipc->busy[ch_index]))
|
|
;
|
|
|
|
kfree(ch);
|
|
return rval;
|
|
}
|
|
} while (mrecv.type != SMSG_TYPE_OPEN || mrecv.flag != SMSG_OPEN_MAGIC);
|
|
|
|
pr_info("%s: channel %d-%d receive open msg!\n",
|
|
__func__, dst, channel);
|
|
|
|
open_done:
|
|
pr_info("%s: channel %d-%d success\n", __func__, dst, channel);
|
|
ipc->states[ch_index] = CHAN_STATE_OPENED;
|
|
atomic_dec(&ipc->busy[ch_index]);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smsg_ch_open);
|
|
|
|
int smsg_ch_close(u8 dst, u8 channel, int timeout)
|
|
{
|
|
struct smsg_ipc *ipc = smsg_ipcs[dst];
|
|
struct smsg_channel *ch;
|
|
struct smsg mclose;
|
|
u8 ch_index;
|
|
|
|
ch_index = channel2index[channel];
|
|
if (ch_index == INVALID_CHANEL_INDEX) {
|
|
pr_err("%s:channel %d invalid!\n", __func__, channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ch = ipc->channels[ch_index];
|
|
if (!ch)
|
|
return 0;
|
|
|
|
smsg_set(&mclose, channel, SMSG_TYPE_CLOSE, SMSG_CLOSE_MAGIC, 0);
|
|
smsg_send(dst, &mclose, timeout);
|
|
|
|
ipc->states[ch_index] = CHAN_STATE_FREE;
|
|
wake_up_interruptible_all(&ch->rxwait);
|
|
|
|
/* wait for the channel being unused */
|
|
while (atomic_read(&ipc->busy[ch_index]))
|
|
;
|
|
|
|
/* maybe channel has been free for smsg_ch_open failed */
|
|
if (ipc->channels[ch_index]) {
|
|
ipc->channels[ch_index] = NULL;
|
|
/* guarantee that channel resource isn't used in irq handler */
|
|
while (atomic_read(&ipc->busy[ch_index]))
|
|
;
|
|
sprd_pms_destroy(ch->rx_pms);
|
|
sprd_pms_destroy(ch->tx_pms);
|
|
kfree(ch);
|
|
}
|
|
|
|
/* finally, update the channel state*/
|
|
ipc->states[ch_index] = CHAN_STATE_UNUSED;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smsg_ch_close);
|
|
|
|
static void smsg_bl_cpoy_msg(struct smsg *dst, struct smsg *src)
|
|
{
|
|
dst->channel = src->channel;
|
|
dst->type = src->type;
|
|
BL_SETW(dst->flag, src->flag);
|
|
BL_SETL(dst->value, src->value);
|
|
}
|
|
|
|
int smsg_senddie(u8 dst)
|
|
{
|
|
struct smsg msg;
|
|
struct smsg_ipc *ipc = smsg_ipcs[dst];
|
|
uintptr_t txpos;
|
|
int rval = 0;
|
|
|
|
if (!ipc)
|
|
return -ENODEV;
|
|
|
|
msg.channel = SMSG_CH_CTRL;
|
|
msg.type = SMSG_TYPE_DIE;
|
|
msg.flag = 0;
|
|
msg.value = 0;
|
|
|
|
#ifdef CONFIG_SPRD_MAILBOX
|
|
if (ipc->type == SIPC_BASE_MBOX) {
|
|
mbox_just_sent(ipc->core_id, *((u64 *)&msg));
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
if (ipc->ring_base) {
|
|
/* must wait resource before read or write share memory */
|
|
rval = sprd_pms_request_resource(ipc->sipc_pms, 0);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
if (((int)(BL_READL(ipc->txbuf_wrptr) -
|
|
BL_READL(ipc->txbuf_rdptr)) >=
|
|
ipc->txbuf_size)) {
|
|
pr_info("%s: smsg txbuf is full!\n", __func__);
|
|
rval = -EBUSY;
|
|
} else {
|
|
/* calc txpos and write smsg */
|
|
txpos = (BL_READL(ipc->txbuf_wrptr) &
|
|
(ipc->txbuf_size - 1)) *
|
|
sizeof(struct smsg) + ipc->txbuf_addr;
|
|
smsg_bl_cpoy_msg((void *)txpos, &msg);
|
|
|
|
/* update wrptr */
|
|
BL_WRITEL(BL_READL(ipc->txbuf_wrptr) + 1,
|
|
ipc->txbuf_wrptr);
|
|
}
|
|
ipc->txirq_trigger(ipc->dst, *((u64 *)&msg));
|
|
sprd_pms_release_resource(ipc->sipc_pms);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smsg_senddie);
|
|
|
|
int smsg_send(u8 dst, struct smsg *msg, int timeout)
|
|
{
|
|
struct smsg_ipc *ipc = smsg_ipcs[dst];
|
|
struct smsg_channel *ch;
|
|
uintptr_t txpos;
|
|
int rval = 0;
|
|
unsigned long flags;
|
|
u8 ch_index;
|
|
|
|
ch_index = channel2index[msg->channel];
|
|
if (ch_index == INVALID_CHANEL_INDEX) {
|
|
pr_err("%s:channel %d invalid!\n", __func__, msg->channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ipc)
|
|
return -ENODEV;
|
|
|
|
if (!ipc->channels[ch_index]) {
|
|
pr_err("%s: channel %d not inited!\n", __func__, msg->channel);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (ipc->states[ch_index] != CHAN_STATE_OPENED &&
|
|
msg->type != SMSG_TYPE_OPEN &&
|
|
msg->type != SMSG_TYPE_CLOSE) {
|
|
pr_err("%s: channel %d not opened!\n", __func__, msg->channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ch = ipc->channels[ch_index];
|
|
|
|
pr_debug("send smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
|
|
msg->channel, msg->type, msg->flag, msg->value);
|
|
|
|
/*
|
|
* Must wait resource before read or write share memory,
|
|
* and must wait resource before trigger irq,
|
|
* And it must before if (ipc->ring_base),
|
|
* because it be inited as the same time as resource ready.
|
|
*/
|
|
rval = sprd_pms_request_resource(ch->tx_pms, timeout);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
if (ipc->ring_base) {
|
|
spin_lock_irqsave(&ipc->txpinlock, flags);
|
|
if (((int)(BL_READL(ipc->txbuf_wrptr) -
|
|
BL_READL(ipc->txbuf_rdptr)) >=
|
|
ipc->txbuf_size)) {
|
|
pr_err("write smsg: txbuf full, wrptr=0x%x, rdptr=0x%x\n",
|
|
BL_READL(ipc->txbuf_wrptr),
|
|
BL_READL(ipc->txbuf_rdptr));
|
|
rval = -EBUSY;
|
|
} else {
|
|
/* calc txpos and write smsg */
|
|
txpos = (BL_READL(ipc->txbuf_wrptr) &
|
|
(ipc->txbuf_size - 1)) *
|
|
sizeof(struct smsg) + ipc->txbuf_addr;
|
|
smsg_bl_cpoy_msg((void *)txpos, msg);
|
|
|
|
/* update wrptr */
|
|
BL_WRITEL(BL_READL(ipc->txbuf_wrptr) + 1,
|
|
ipc->txbuf_wrptr);
|
|
}
|
|
spin_unlock_irqrestore(&ipc->txpinlock, flags);
|
|
} else if (ipc->type != SIPC_BASE_MBOX) {
|
|
pr_err("send smsg:ring_base is NULL");
|
|
sprd_pms_release_resource(ch->tx_pms);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ipc->txirq_trigger(ipc->dst, *(u64 *)msg);
|
|
sprd_pms_release_resource(ch->tx_pms);
|
|
|
|
return rval;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smsg_send);
|
|
|
|
int smsg_recv(u8 dst, struct smsg *msg, int timeout)
|
|
{
|
|
struct smsg_ipc *ipc = smsg_ipcs[dst];
|
|
struct smsg_channel *ch;
|
|
u32 rd;
|
|
int rval = 0;
|
|
u8 ch_index;
|
|
|
|
ch_index = channel2index[msg->channel];
|
|
if (ch_index == INVALID_CHANEL_INDEX) {
|
|
pr_err("%s:channel %d invalid!\n", __func__, msg->channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ipc)
|
|
return -ENODEV;
|
|
|
|
atomic_inc(&ipc->busy[ch_index]);
|
|
|
|
ch = ipc->channels[ch_index];
|
|
|
|
if (!ch) {
|
|
pr_err("%s: channel %d not opened!\n", __func__, msg->channel);
|
|
atomic_dec(&ipc->busy[ch_index]);
|
|
return -ENODEV;
|
|
}
|
|
|
|
pr_debug("%s: dst=%d, channel=%d, timeout=%d, ch_index = %d\n",
|
|
__func__, dst, msg->channel, timeout, ch_index);
|
|
|
|
if (timeout == 0) {
|
|
if (!mutex_trylock(&ch->rxlock)) {
|
|
pr_err("dst=%d, channel=%d recv smsg busy!\n",
|
|
dst, msg->channel);
|
|
atomic_dec(&ipc->busy[ch_index]);
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* no wait */
|
|
if (SIPC_READL(ch->wrptr) == SIPC_READL(ch->rdptr)) {
|
|
pr_info("dst=%d, channel=%d smsg rx cache is empty!\n",
|
|
dst, msg->channel);
|
|
|
|
rval = -ENODATA;
|
|
|
|
goto recv_failed;
|
|
}
|
|
} else if (timeout < 0) {
|
|
mutex_lock_interruptible(&ch->rxlock);
|
|
/* wait forever */
|
|
rval = wait_event_interruptible(
|
|
ch->rxwait,
|
|
(SIPC_READL(ch->wrptr) !=
|
|
SIPC_READL(ch->rdptr)) ||
|
|
(ipc->states[ch_index] == CHAN_STATE_FREE));
|
|
if (rval < 0) {
|
|
pr_debug("%s: dst=%d, channel=%d wait interrupted!\n",
|
|
__func__, dst, msg->channel);
|
|
|
|
goto recv_failed;
|
|
}
|
|
|
|
if (ipc->states[ch_index] == CHAN_STATE_FREE) {
|
|
pr_info("%s: dst=%d, channel=%d channel is free!\n",
|
|
__func__, dst, msg->channel);
|
|
|
|
rval = -EIO;
|
|
|
|
goto recv_failed;
|
|
}
|
|
} else {
|
|
mutex_lock_interruptible(&ch->rxlock);
|
|
/* wait timeout */
|
|
rval = wait_event_interruptible_timeout(
|
|
ch->rxwait,
|
|
(SIPC_READL(ch->wrptr) != SIPC_READL(ch->rdptr)) ||
|
|
(ipc->states[ch_index] == CHAN_STATE_FREE),
|
|
timeout);
|
|
if (rval < 0) {
|
|
pr_debug("%s: dst=%d, channel=%d wait interrupted!\n",
|
|
__func__, dst, msg->channel);
|
|
|
|
goto recv_failed;
|
|
} else if (rval == 0) {
|
|
pr_debug("%s: dst=%d, channel=%d wait timeout!\n",
|
|
__func__, dst, msg->channel);
|
|
|
|
rval = -ETIME;
|
|
|
|
goto recv_failed;
|
|
}
|
|
|
|
if (ipc->states[ch_index] == CHAN_STATE_FREE) {
|
|
pr_info("%s: dst=%d, channel=%d channel is free!\n",
|
|
__func__, dst, msg->channel);
|
|
|
|
rval = -EIO;
|
|
|
|
goto recv_failed;
|
|
}
|
|
}
|
|
|
|
/* read smsg from cache */
|
|
rd = SIPC_READL(ch->rdptr) & (SMSG_CACHE_NR - 1);
|
|
memcpy(msg, &ch->caches[rd], sizeof(struct smsg));
|
|
SIPC_WRITEL(SIPC_READL(ch->rdptr) + 1, ch->rdptr);
|
|
|
|
if (ipc->ring_base)
|
|
pr_debug("read smsg: dst=%d, channel=%d, wrptr=%d, rdptr=%d, rd=%d\n",
|
|
dst,
|
|
msg->channel,
|
|
SIPC_READL(ch->wrptr),
|
|
SIPC_READL(ch->rdptr),
|
|
rd);
|
|
|
|
pr_debug("recv smsg: dst=%d, channel=%d, type=%d, flag=0x%04x, value=0x%08x, rval = %d\n",
|
|
dst, msg->channel, msg->type, msg->flag, msg->value, rval);
|
|
|
|
recv_failed:
|
|
mutex_unlock(&ch->rxlock);
|
|
atomic_dec(&ipc->busy[ch_index]);
|
|
return rval;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smsg_recv);
|
|
|
|
u8 sipc_channel2index(u8 channel)
|
|
{
|
|
return channel2index[channel];
|
|
}
|
|
EXPORT_SYMBOL_GPL(sipc_channel2index);
|
|
|
|
#if defined(CONFIG_DEBUG_FS)
|
|
static int smsg_debug_show(struct seq_file *m, void *private)
|
|
{
|
|
struct smsg_ipc *ipc = NULL;
|
|
struct smsg_channel *ch;
|
|
|
|
int i, j, cnt;
|
|
|
|
for (i = 0; i < SIPC_ID_NR; i++) {
|
|
ipc = smsg_ipcs[i];
|
|
if (!ipc)
|
|
continue;
|
|
|
|
sipc_debug_putline(m, '*', 120);
|
|
seq_printf(m, "sipc: %s:\n", ipc->name);
|
|
seq_printf(m, "dst: 0x%0x, irq: 0x%0x\n",
|
|
ipc->dst, ipc->irq);
|
|
if (ipc->ring_base) {
|
|
/*
|
|
* must wait resource before
|
|
* read or write share memory.
|
|
*/
|
|
if (sipc_smem_request_resource(ipc->sipc_pms,
|
|
ipc->dst, 1000) < 0)
|
|
continue;
|
|
|
|
seq_printf(m, "txbufAddr: 0x%p, txbufsize: 0x%x, txbufrdptr: [0x%p]=%d, txbufwrptr: [0x%p]=%d\n",
|
|
(void *)ipc->txbuf_addr,
|
|
ipc->txbuf_size,
|
|
(void *)ipc->txbuf_rdptr,
|
|
BL_READL(ipc->txbuf_rdptr),
|
|
(void *)ipc->txbuf_wrptr,
|
|
BL_READL(ipc->txbuf_wrptr));
|
|
seq_printf(m, "rxbufAddr: 0x%p, rxbufsize: 0x%x, rxbufrdptr: [0x%p]=%d, rxbufwrptr: [0x%p]=%d\n",
|
|
(void *)ipc->rxbuf_addr,
|
|
ipc->rxbuf_size,
|
|
(void *)ipc->rxbuf_rdptr,
|
|
BL_READL(ipc->rxbuf_rdptr),
|
|
(void *)ipc->rxbuf_wrptr,
|
|
BL_READL(ipc->rxbuf_wrptr));
|
|
|
|
/* release resource */
|
|
sipc_smem_release_resource(ipc->sipc_pms, ipc->dst);
|
|
}
|
|
sipc_debug_putline(m, '-', 80);
|
|
seq_puts(m, "1. all channel state list:\n");
|
|
|
|
for (j = 0; j < SMSG_VALID_CH_NR; j++)
|
|
seq_printf(m,
|
|
"%2d. channel[%3d] states: %d, name: %s\n",
|
|
j,
|
|
sipc_cfg[j].channel,
|
|
ipc->states[j],
|
|
sipc_cfg[j].name);
|
|
|
|
sipc_debug_putline(m, '-', 80);
|
|
seq_puts(m, "2. channel rdpt < wrpt list:\n");
|
|
|
|
cnt = 1;
|
|
for (j = 0; j < SMSG_VALID_CH_NR; j++) {
|
|
ch = ipc->channels[j];
|
|
if (!ch)
|
|
continue;
|
|
|
|
if (SIPC_READL(ch->rdptr) < SIPC_READL(ch->wrptr))
|
|
seq_printf(m, "%2d. channel[%3d] rd: %d, wt: %d, name: %s\n",
|
|
cnt++,
|
|
sipc_cfg[j].channel,
|
|
SIPC_READL(ch->rdptr),
|
|
SIPC_READL(ch->wrptr),
|
|
sipc_cfg[j].name);
|
|
}
|
|
}
|
|
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", 0444,
|
|
(struct dentry *)root,
|
|
NULL,
|
|
&smsg_debug_fops);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smsg_init_debugfs);
|
|
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
|
|
MODULE_AUTHOR("Chen Gaopeng");
|
|
MODULE_DESCRIPTION("SIPC/SMSG driver");
|
|
MODULE_LICENSE("GPL v2");
|