blob: 6559a282a99dfa11fffc304091f2affab261dac1 [file] [log] [blame]
/*
* Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nvshm_types.h"
#include "nvshm_if.h"
#include "nvshm_priv.h"
#include "nvshm_iobuf.h"
#include "nvshm_ipc.h"
#include "nvshm_queue.h"
#include <linux/interrupt.h>
#include <asm/mach/map.h>
#include <mach/tegra_bb.h>
#include <asm/cacheflush.h>
#define NVSHM_WAKE_TIMEOUT_NS (20 * NSEC_PER_MSEC)
#define NVSHM_WAKE_MAX_COUNT (50)
static int ipc_readconfig(struct nvshm_handle *handle)
{
struct nvshm_config *conf;
int chan;
pr_debug("%s\n", __func__);
conf = (struct nvshm_config *)(handle->mb_base_virt
+ NVSHM_CONFIG_OFFSET);
/* No change in v2.x kernel prevents from running v1.3 modems, so let's
* ensure some continuity of service.
*/
if ((conf->version == NVSHM_CONFIG_VERSION_1_3) &&
(NVSHM_MAJOR(NVSHM_CONFIG_VERSION) == 2)) {
pr_warn("%s BBC version 1.3, statistics not available\n",
__func__);
} else if (NVSHM_MAJOR(conf->version) !=
NVSHM_MAJOR(NVSHM_CONFIG_VERSION)) {
pr_err("%s SHM version mismatch: BBC: %d.%d / AP: %d.%d\n",
__func__,
NVSHM_MAJOR(conf->version),
NVSHM_MINOR(conf->version),
NVSHM_MAJOR(NVSHM_CONFIG_VERSION),
NVSHM_MINOR(NVSHM_CONFIG_VERSION));
return -1;
} else if (NVSHM_MINOR(conf->version) !=
NVSHM_MINOR(NVSHM_CONFIG_VERSION)) {
pr_warn("%s SHM versions differ: BBC: %d.%d / AP: %d.%d\n",
__func__,
NVSHM_MAJOR(conf->version),
NVSHM_MINOR(conf->version),
NVSHM_MAJOR(NVSHM_CONFIG_VERSION),
NVSHM_MINOR(NVSHM_CONFIG_VERSION));
}
if (handle->ipc_size != conf->shmem_size) {
pr_warn("%s shmem mapped/reported not matching: 0x%x/0x%x\n",
__func__, (unsigned int)handle->ipc_size,
conf->shmem_size);
}
handle->desc_base_virt = handle->ipc_base_virt
+ conf->region_ap_desc_offset;
pr_debug("%s desc_base_virt=0x%p\n",
__func__, handle->desc_base_virt);
handle->desc_size = conf->region_ap_desc_size;
pr_debug("%s desc_size=%d\n",
__func__, (int)handle->desc_size);
/* Data is cached */
handle->data_base_virt = handle->ipc_base_virt
+ conf->region_ap_data_offset;
pr_debug("%s data_base_virt=0x%p\n",
__func__, handle->data_base_virt);
handle->data_size = conf->region_ap_data_size;
pr_debug("%s data_size=%d\n", __func__, (int)handle->data_size);
if (NVSHM_MAJOR(conf->version) < 2) {
handle->stats_base_virt = 0;
handle->stats_size = 0;
} else {
handle->stats_base_virt = handle->mb_base_virt
+ conf->region_dxp1_stats_offset;
pr_debug("%s stats_base_virt=0x%p\n",
__func__, handle->stats_base_virt);
handle->stats_size = conf->region_dxp1_stats_size;
pr_debug("%s stats_size=%lu\n", __func__, handle->stats_size);
}
#ifndef CONFIG_TEGRA_BASEBAND_SIMU
handle->shared_queue_head =
(struct nvshm_iobuf *)(handle->ipc_base_virt
+ conf->queue_bb_offset);
pr_debug("%s shared_queue_head offset=0x%lx\n",
__func__,
(long)handle->shared_queue_head - (long)handle->ipc_base_virt);
#else
handle->shared_queue_head =
(struct nvshm_iobuf *)(handle->ipc_base_virt
+ conf->queue_ap_offset);
pr_debug("%s shared_queue_head offset=0x%lx\n",
__func__,
(long)handle->shared_queue_head - (long)handle->ipc_base_virt);
#endif
handle->shared_queue_tail =
(struct nvshm_iobuf *)(handle->ipc_base_virt
+ conf->queue_ap_offset);
pr_debug("%s shared_queue_tail offset=0x%lx\n",
__func__, (long)handle->shared_queue_tail -
(long)handle->ipc_base_virt);
for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
handle->chan[chan].index = chan;
handle->chan[chan].map = conf->chan_map[chan];
if (handle->chan[chan].map.type != NVSHM_CHAN_UNMAP) {
pr_debug("%s chan[%d]=%s\n",
__func__, chan, handle->chan[chan].map.name);
}
}
/* Serial number (e.g BBC PCID) */
tegra_bb_set_ipc_serial(handle->tegra_bb, conf->serial);
/* Invalidate cache for IPC region before use */
INV_CPU_DCACHE(handle->ipc_base_virt, handle->ipc_size);
handle->conf = conf;
handle->configured = 1;
return 0;
}
static int init_interfaces(struct nvshm_handle *handle)
{
int nlog = 0, ntty = 0, nnet = 0, nrpc = 0;
int chan;
for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
handle->chan[chan].xoff = 0;
switch (handle->chan[chan].map.type) {
case NVSHM_CHAN_UNMAP:
break;
case NVSHM_CHAN_TTY:
case NVSHM_CHAN_LOG:
ntty++;
handle->chan[chan].rate_counter = NVSHM_RATE_LIMIT_TTY;
break;
case NVSHM_CHAN_NET:
handle->chan[chan].rate_counter = NVSHM_RATE_LIMIT_NET;
nnet++;
break;
case NVSHM_CHAN_RPC:
handle->chan[chan].rate_counter = NVSHM_RATE_LIMIT_RPC;
nrpc++;
break;
default:
break;
}
}
if (ntty) {
pr_debug("%s init %d tty channels\n", __func__, ntty);
nvshm_tty_init(handle);
}
if (nlog)
pr_debug("%s init %d log channels\n", __func__, nlog);
if (nnet) {
pr_debug("%s init %d net channels\n", __func__, nnet);
nvshm_net_init(handle);
}
if (nrpc) {
pr_debug("%s init %d rpc channels\n", __func__, nrpc);
nvshm_rpc_init(handle);
nvshm_rpc_dispatcher_init();
}
pr_debug("%s init statistics support\n", __func__);
nvshm_stats_init(handle);
return 0;
}
static int cleanup_interfaces(struct nvshm_handle *handle)
{
int nlog = 0, ntty = 0, nnet = 0, nrpc = 0;
int chan;
/* No need to protect this as configuration will arrive after cleanup
* is propagated to userland
*/
handle->configured = 0;
for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
switch (handle->chan[chan].map.type) {
case NVSHM_CHAN_TTY:
case NVSHM_CHAN_LOG:
ntty++;
break;
case NVSHM_CHAN_NET:
nnet++;
break;
case NVSHM_CHAN_RPC:
nrpc++;
break;
default:
break;
}
}
if (ntty) {
pr_debug("%s cleanup %d tty channels\n", __func__, ntty);
nvshm_tty_cleanup();
}
if (nlog)
pr_debug("%s cleanup %d log channels\n", __func__, nlog);
if (nnet) {
pr_debug("%s cleanup %d net channels\n", __func__, nnet);
nvshm_net_cleanup();
}
if (nrpc) {
pr_debug("%s cleanup %d rpc channels\n", __func__, nrpc);
nvshm_rpc_dispatcher_cleanup();
nvshm_rpc_cleanup();
}
pr_debug("%s cleanup statistics support\n", __func__);
nvshm_stats_cleanup();
/* Remove serial sysfs entry */
tegra_bb_set_ipc_serial(handle->tegra_bb, NULL);
return 0;
}
static void ipc_work(struct work_struct *work)
{
struct nvshm_handle *handle = container_of(work,
struct nvshm_handle,
nvshm_work);
int new_state;
int cmd;
if (!wake_lock_active(&handle->dl_lock))
wake_lock(&handle->dl_lock);
new_state = *((int *)handle->mb_base_virt);
cmd = new_state & 0xFFFF;
if (((~new_state >> 16) ^ (cmd)) & 0xFFFF) {
pr_err("%s: IPC check failure msg=0x%x\n",
__func__, new_state);
if (handle->configured) {
nvshm_abort_queue(handle);
cleanup_interfaces(handle);
}
goto ipc_exit;
}
switch (cmd) {
case NVSHM_IPC_READY:
/* most encountered message - process queue */
if (cmd != handle->old_status) {
if (ipc_readconfig(handle))
goto ipc_exit;
nvshm_iobuf_init(handle);
nvshm_init_queue(handle);
init_interfaces(handle);
}
/* Process IPC queue but do not notify sysfs */
if (handle->configured) {
nvshm_process_queue(handle);
if (handle->errno) {
pr_err("%s: cleanup interfaces\n",
__func__);
nvshm_abort_queue(handle);
cleanup_interfaces(handle);
break;
}
}
break;
case NVSHM_IPC_BOOT_FW_REQ:
case NVSHM_IPC_BOOT_RESTART_FW_REQ:
if (handle->configured) {
nvshm_abort_queue(handle);
cleanup_interfaces(handle);
pr_debug("%s: cleanup done\n", __func__);
}
break;
case NVSHM_IPC_BOOT_ERROR_BT2_HDR:
case NVSHM_IPC_BOOT_ERROR_BT2_SIGN:
case NVSHM_IPC_BOOT_ERROR_HWID:
case NVSHM_IPC_BOOT_ERROR_APP_HDR:
case NVSHM_IPC_BOOT_ERROR_APP_SIGN:
case NVSHM_IPC_BOOT_ERROR_UNLOCK_HEADER:
case NVSHM_IPC_BOOT_ERROR_UNLOCK_SIGN:
case NVSHM_IPC_BOOT_ERROR_UNLOCK_PCID:
pr_err("%s BB startup failure: msg=0x%x\n",
__func__, new_state);
break;
case NVSHM_IPC_BOOT_COLD_BOOT_IND:
case NVSHM_IPC_BOOT_FW_CONF:
/* Should not have these - something went wrong... */
pr_err("%s IPC IT error: msg=0x%x\n",
__func__, new_state);
break;
default:
pr_err("%s unknown IPC message found: msg=0x%x\n",
__func__, new_state);
}
handle->old_status = cmd;
ipc_exit:
wake_unlock(&handle->dl_lock);
enable_irq(handle->bb_irq);
}
static void start_tx_worker(struct work_struct *work)
{
struct nvshm_channel *chan = container_of(work,
struct nvshm_channel,
start_tx_work);
pr_warn("%s: start tx on chan %d\n", __func__, chan->index);
if (chan->ops)
chan->ops->start_tx(chan);
}
static void nvshm_ipc_handler(void *data)
{
struct nvshm_handle *handle = (struct nvshm_handle *)data;
int ret;
pr_debug("%s\n", __func__);
ret = queue_work(handle->nvshm_wq, &handle->nvshm_work);
}
static enum hrtimer_restart nvshm_ipc_timer_func(struct hrtimer *timer)
{
struct nvshm_handle *handle =
container_of(timer, struct nvshm_handle, wake_timer);
if (tegra_bb_check_ipc(handle->tegra_bb) == 1) {
pr_debug("%s AP2BB is cleared\n", __func__);
wake_unlock(&handle->ul_lock);
return HRTIMER_NORESTART;
}
if (handle->timeout++ > NVSHM_WAKE_MAX_COUNT) {
pr_warn("%s AP2BB not cleared in 1s - aborting\n", __func__);
tegra_bb_abort_ipc(handle->tegra_bb);
wake_unlock(&handle->ul_lock);
return HRTIMER_NORESTART;
}
pr_debug("%s AP2BB is still set\n", __func__);
hrtimer_forward_now(timer, ktime_set(0, NVSHM_WAKE_TIMEOUT_NS));
return HRTIMER_RESTART;
}
int nvshm_register_ipc(struct nvshm_handle *handle)
{
int chan;
pr_debug("%s\n", __func__);
snprintf(handle->wq_name, 15, "nvshm_queue%d", handle->instance);
handle->nvshm_wq = create_singlethread_workqueue(handle->wq_name);
INIT_WORK(&handle->nvshm_work, ipc_work);
for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++)
INIT_WORK(&handle->chan[chan].start_tx_work, start_tx_worker);
hrtimer_init(&handle->wake_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
handle->wake_timer.function = nvshm_ipc_timer_func;
tegra_bb_register_ipc(handle->tegra_bb, nvshm_ipc_handler, handle);
return 0;
}
int nvshm_unregister_ipc(struct nvshm_handle *handle)
{
pr_debug("%s flush workqueue\n", __func__);
flush_workqueue(handle->nvshm_wq);
pr_debug("%s destroy workqueue\n", __func__);
destroy_workqueue(handle->nvshm_wq);
pr_debug("%s unregister tegra_bb\n", __func__);
tegra_bb_register_ipc(handle->tegra_bb, NULL, NULL);
hrtimer_cancel(&handle->wake_timer);
return 0;
}
int nvshm_generate_ipc(struct nvshm_handle *handle)
{
/* take wake lock until BB ack our irq */
if (!wake_lock_active(&handle->ul_lock))
wake_lock(&handle->ul_lock);
if (!hrtimer_active(&handle->wake_timer)) {
handle->timeout = 0;
hrtimer_start(&handle->wake_timer,
ktime_set(0, NVSHM_WAKE_TIMEOUT_NS),
HRTIMER_MODE_REL);
}
/* generate ipc */
tegra_bb_generate_ipc(handle->tegra_bb);
return 0;
}