![Justin P. Mattock](/assets/img/avatar_default.png)
Signed-off-by: Justin P. Mattock <justinmattock@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2246 lines
59 KiB
C
2246 lines
59 KiB
C
/*
|
|
* io_sm.c
|
|
*
|
|
* DSP-BIOS Bridge driver support functions for TI OMAP processors.
|
|
*
|
|
* IO dispatcher for a shared memory channel driver.
|
|
*
|
|
* Copyright (C) 2005-2006 Texas Instruments, Inc.
|
|
*
|
|
* This package is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
/*
|
|
* Channel Invariant:
|
|
* There is an important invariant condition which must be maintained per
|
|
* channel outside of bridge_chnl_get_ioc() and IO_Dispatch(), violation of
|
|
* which may cause timeouts and/or failure of the sync_wait_on_event
|
|
* function.
|
|
*/
|
|
#include <linux/types.h>
|
|
#include <linux/list.h>
|
|
|
|
/* Host OS */
|
|
#include <dspbridge/host_os.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
/* ----------------------------------- DSP/BIOS Bridge */
|
|
#include <dspbridge/dbdefs.h>
|
|
|
|
/* Services Layer */
|
|
#include <dspbridge/ntfy.h>
|
|
#include <dspbridge/sync.h>
|
|
|
|
/* Hardware Abstraction Layer */
|
|
#include <hw_defs.h>
|
|
#include <hw_mmu.h>
|
|
|
|
/* Bridge Driver */
|
|
#include <dspbridge/dspdeh.h>
|
|
#include <dspbridge/dspio.h>
|
|
#include <dspbridge/dspioctl.h>
|
|
#include <dspbridge/wdt.h>
|
|
#include <_tiomap.h>
|
|
#include <tiomap_io.h>
|
|
#include <_tiomap_pwr.h>
|
|
|
|
/* Platform Manager */
|
|
#include <dspbridge/cod.h>
|
|
#include <dspbridge/node.h>
|
|
#include <dspbridge/dev.h>
|
|
|
|
/* Others */
|
|
#include <dspbridge/rms_sh.h>
|
|
#include <dspbridge/mgr.h>
|
|
#include <dspbridge/drv.h>
|
|
#include "_cmm.h"
|
|
#include "module_list.h"
|
|
|
|
/* This */
|
|
#include <dspbridge/io_sm.h>
|
|
#include "_msg_sm.h"
|
|
|
|
/* Defines, Data Structures, Typedefs */
|
|
#define OUTPUTNOTREADY 0xffff
|
|
#define NOTENABLED 0xffff /* Channel(s) not enabled */
|
|
|
|
#define EXTEND "_EXT_END"
|
|
|
|
#define SWAP_WORD(x) (x)
|
|
#define UL_PAGE_ALIGN_SIZE 0x10000 /* Page Align Size */
|
|
|
|
#define MAX_PM_REQS 32
|
|
|
|
#define MMU_FAULT_HEAD1 0xa5a5a5a5
|
|
#define MMU_FAULT_HEAD2 0x96969696
|
|
#define POLL_MAX 1000
|
|
#define MAX_MMU_DBGBUFF 10240
|
|
|
|
/* IO Manager: only one created per board */
|
|
struct io_mgr {
|
|
/* These four fields must be the first fields in a io_mgr_ struct */
|
|
/* Bridge device context */
|
|
struct bridge_dev_context *bridge_context;
|
|
/* Function interface to Bridge driver */
|
|
struct bridge_drv_interface *intf_fxns;
|
|
struct dev_object *dev_obj; /* Device this board represents */
|
|
|
|
/* These fields initialized in bridge_io_create() */
|
|
struct chnl_mgr *chnl_mgr;
|
|
struct shm *shared_mem; /* Shared Memory control */
|
|
u8 *input; /* Address of input channel */
|
|
u8 *output; /* Address of output channel */
|
|
struct msg_mgr *msg_mgr; /* Message manager */
|
|
/* Msg control for from DSP messages */
|
|
struct msg_ctrl *msg_input_ctrl;
|
|
/* Msg control for to DSP messages */
|
|
struct msg_ctrl *msg_output_ctrl;
|
|
u8 *msg_input; /* Address of input messages */
|
|
u8 *msg_output; /* Address of output messages */
|
|
u32 sm_buf_size; /* Size of a shared memory I/O channel */
|
|
bool shared_irq; /* Is this IRQ shared? */
|
|
u32 word_size; /* Size in bytes of DSP word */
|
|
u16 intr_val; /* Interrupt value */
|
|
/* Private extnd proc info; mmu setup */
|
|
struct mgr_processorextinfo ext_proc_info;
|
|
struct cmm_object *cmm_mgr; /* Shared Mem Mngr */
|
|
struct work_struct io_workq; /* workqueue */
|
|
#if defined(CONFIG_TIDSPBRIDGE_BACKTRACE)
|
|
u32 trace_buffer_begin; /* Trace message start address */
|
|
u32 trace_buffer_end; /* Trace message end address */
|
|
u32 trace_buffer_current; /* Trace message current address */
|
|
u32 gpp_read_pointer; /* GPP Read pointer to Trace buffer */
|
|
u8 *msg;
|
|
u32 gpp_va;
|
|
u32 dsp_va;
|
|
#endif
|
|
/* IO Dpc */
|
|
u32 dpc_req; /* Number of requested DPC's. */
|
|
u32 dpc_sched; /* Number of executed DPC's. */
|
|
struct tasklet_struct dpc_tasklet;
|
|
spinlock_t dpc_lock;
|
|
|
|
};
|
|
|
|
struct shm_symbol_val {
|
|
u32 shm_base;
|
|
u32 shm_lim;
|
|
u32 msg_base;
|
|
u32 msg_lim;
|
|
u32 shm0_end;
|
|
u32 dyn_ext;
|
|
u32 ext_end;
|
|
};
|
|
|
|
/* Function Prototypes */
|
|
static void io_dispatch_pm(struct io_mgr *pio_mgr);
|
|
static void notify_chnl_complete(struct chnl_object *pchnl,
|
|
struct chnl_irp *chnl_packet_obj);
|
|
static void input_chnl(struct io_mgr *pio_mgr, struct chnl_object *pchnl,
|
|
u8 io_mode);
|
|
static void output_chnl(struct io_mgr *pio_mgr, struct chnl_object *pchnl,
|
|
u8 io_mode);
|
|
static void input_msg(struct io_mgr *pio_mgr, struct msg_mgr *hmsg_mgr);
|
|
static void output_msg(struct io_mgr *pio_mgr, struct msg_mgr *hmsg_mgr);
|
|
static u32 find_ready_output(struct chnl_mgr *chnl_mgr_obj,
|
|
struct chnl_object *pchnl, u32 mask);
|
|
|
|
/* Bus Addr (cached kernel) */
|
|
static int register_shm_segs(struct io_mgr *hio_mgr,
|
|
struct cod_manager *cod_man,
|
|
u32 dw_gpp_base_pa);
|
|
|
|
static inline void set_chnl_free(struct shm *sm, u32 chnl)
|
|
{
|
|
sm->host_free_mask &= ~(1 << chnl);
|
|
}
|
|
|
|
static inline void set_chnl_busy(struct shm *sm, u32 chnl)
|
|
{
|
|
sm->host_free_mask |= 1 << chnl;
|
|
}
|
|
|
|
|
|
/*
|
|
* ======== bridge_io_create ========
|
|
* Create an IO manager object.
|
|
*/
|
|
int bridge_io_create(struct io_mgr **io_man,
|
|
struct dev_object *hdev_obj,
|
|
const struct io_attrs *mgr_attrts)
|
|
{
|
|
struct io_mgr *pio_mgr = NULL;
|
|
struct bridge_dev_context *hbridge_context = NULL;
|
|
struct cfg_devnode *dev_node_obj;
|
|
struct chnl_mgr *hchnl_mgr;
|
|
u8 dev_type;
|
|
|
|
/* Check requirements */
|
|
if (!io_man || !mgr_attrts || mgr_attrts->word_size == 0)
|
|
return -EFAULT;
|
|
|
|
*io_man = NULL;
|
|
|
|
dev_get_chnl_mgr(hdev_obj, &hchnl_mgr);
|
|
if (!hchnl_mgr || hchnl_mgr->iomgr)
|
|
return -EFAULT;
|
|
|
|
/*
|
|
* Message manager will be created when a file is loaded, since
|
|
* size of message buffer in shared memory is configurable in
|
|
* the base image.
|
|
*/
|
|
dev_get_bridge_context(hdev_obj, &hbridge_context);
|
|
if (!hbridge_context)
|
|
return -EFAULT;
|
|
|
|
dev_get_dev_type(hdev_obj, &dev_type);
|
|
|
|
/* Allocate IO manager object */
|
|
pio_mgr = kzalloc(sizeof(struct io_mgr), GFP_KERNEL);
|
|
if (!pio_mgr)
|
|
return -ENOMEM;
|
|
|
|
/* Initialize chnl_mgr object */
|
|
pio_mgr->chnl_mgr = hchnl_mgr;
|
|
pio_mgr->word_size = mgr_attrts->word_size;
|
|
|
|
if (dev_type == DSP_UNIT) {
|
|
/* Create an IO DPC */
|
|
tasklet_init(&pio_mgr->dpc_tasklet, io_dpc, (u32) pio_mgr);
|
|
|
|
/* Initialize DPC counters */
|
|
pio_mgr->dpc_req = 0;
|
|
pio_mgr->dpc_sched = 0;
|
|
|
|
spin_lock_init(&pio_mgr->dpc_lock);
|
|
|
|
if (dev_get_dev_node(hdev_obj, &dev_node_obj)) {
|
|
bridge_io_destroy(pio_mgr);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
pio_mgr->bridge_context = hbridge_context;
|
|
pio_mgr->shared_irq = mgr_attrts->irq_shared;
|
|
if (dsp_wdt_init()) {
|
|
bridge_io_destroy(pio_mgr);
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Return IO manager object to caller... */
|
|
hchnl_mgr->iomgr = pio_mgr;
|
|
*io_man = pio_mgr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_io_destroy ========
|
|
* Purpose:
|
|
* Disable interrupts, destroy the IO manager.
|
|
*/
|
|
int bridge_io_destroy(struct io_mgr *hio_mgr)
|
|
{
|
|
int status = 0;
|
|
if (hio_mgr) {
|
|
/* Free IO DPC object */
|
|
tasklet_kill(&hio_mgr->dpc_tasklet);
|
|
|
|
#if defined(CONFIG_TIDSPBRIDGE_BACKTRACE)
|
|
kfree(hio_mgr->msg);
|
|
#endif
|
|
dsp_wdt_exit();
|
|
/* Free this IO manager object */
|
|
kfree(hio_mgr);
|
|
} else {
|
|
status = -EFAULT;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
struct shm_symbol_val *_get_shm_symbol_values(struct io_mgr *hio_mgr)
|
|
{
|
|
struct shm_symbol_val *s;
|
|
struct cod_manager *cod_man;
|
|
int status;
|
|
|
|
s = kzalloc(sizeof(*s), GFP_KERNEL);
|
|
if (!s)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
status = dev_get_cod_mgr(hio_mgr->dev_obj, &cod_man);
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
/* Get start and length of channel part of shared memory */
|
|
status = cod_get_sym_value(cod_man, CHNL_SHARED_BUFFER_BASE_SYM,
|
|
&s->shm_base);
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
status = cod_get_sym_value(cod_man, CHNL_SHARED_BUFFER_LIMIT_SYM,
|
|
&s->shm_lim);
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
if (s->shm_lim <= s->shm_base) {
|
|
status = -EINVAL;
|
|
goto free_symbol;
|
|
}
|
|
|
|
/* Get start and length of message part of shared memory */
|
|
status = cod_get_sym_value(cod_man, MSG_SHARED_BUFFER_BASE_SYM,
|
|
&s->msg_base);
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
status = cod_get_sym_value(cod_man, MSG_SHARED_BUFFER_LIMIT_SYM,
|
|
&s->msg_lim);
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
if (s->msg_lim <= s->msg_base) {
|
|
status = -EINVAL;
|
|
goto free_symbol;
|
|
}
|
|
|
|
#ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
|
|
status = cod_get_sym_value(cod_man, DSP_TRACESEC_END, &s->shm0_end);
|
|
#else
|
|
status = cod_get_sym_value(cod_man, SHM0_SHARED_END_SYM, &s->shm0_end);
|
|
#endif
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
status = cod_get_sym_value(cod_man, DYNEXTBASE, &s->dyn_ext);
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
status = cod_get_sym_value(cod_man, EXTEND, &s->ext_end);
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
return s;
|
|
|
|
free_symbol:
|
|
kfree(s);
|
|
return ERR_PTR(status);
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_io_on_loaded ========
|
|
* Purpose:
|
|
* Called when a new program is loaded to get shared memory buffer
|
|
* parameters from COFF file. ulSharedBufferBase and ulSharedBufferLimit
|
|
* are in DSP address units.
|
|
*/
|
|
int bridge_io_on_loaded(struct io_mgr *hio_mgr)
|
|
{
|
|
struct bridge_dev_context *dc = hio_mgr->bridge_context;
|
|
struct cfg_hostres *cfg_res = dc->resources;
|
|
struct bridge_ioctl_extproc *eproc;
|
|
struct cod_manager *cod_man;
|
|
struct chnl_mgr *hchnl_mgr;
|
|
struct msg_mgr *hmsg_mgr;
|
|
struct shm_symbol_val *s;
|
|
int status;
|
|
u8 num_procs;
|
|
s32 ndx;
|
|
u32 i;
|
|
u32 mem_sz, msg_sz, pad_sz, shm_sz, shm_base_offs;
|
|
u32 seg0_sz, seg1_sz;
|
|
u32 pa, va, da;
|
|
u32 pa_curr, va_curr, da_curr;
|
|
u32 bytes;
|
|
u32 all_bits = 0;
|
|
u32 page_size[] = {
|
|
HW_PAGE_SIZE16MB, HW_PAGE_SIZE1MB,
|
|
HW_PAGE_SIZE64KB, HW_PAGE_SIZE4KB
|
|
};
|
|
u32 map_attrs = DSP_MAPLITTLEENDIAN | DSP_MAPPHYSICALADDR |
|
|
DSP_MAPELEMSIZE32 | DSP_MAPDONOTLOCK;
|
|
|
|
status = dev_get_cod_mgr(hio_mgr->dev_obj, &cod_man);
|
|
if (status)
|
|
return status;
|
|
|
|
hchnl_mgr = hio_mgr->chnl_mgr;
|
|
|
|
/* The message manager is destroyed when the board is stopped */
|
|
dev_get_msg_mgr(hio_mgr->dev_obj, &hio_mgr->msg_mgr);
|
|
hmsg_mgr = hio_mgr->msg_mgr;
|
|
if (!hchnl_mgr || !hmsg_mgr)
|
|
return -EFAULT;
|
|
|
|
if (hio_mgr->shared_mem)
|
|
hio_mgr->shared_mem = NULL;
|
|
|
|
s = _get_shm_symbol_values(hio_mgr);
|
|
if (IS_ERR(s))
|
|
return PTR_ERR(s);
|
|
|
|
/* Get total length in bytes */
|
|
shm_sz = (s->shm_lim - s->shm_base + 1) * hio_mgr->word_size;
|
|
|
|
/* Calculate size of a PROCCOPY shared memory region */
|
|
dev_dbg(bridge, "%s: (proc)proccopy shmmem size: 0x%x bytes\n",
|
|
__func__, shm_sz - sizeof(struct shm));
|
|
|
|
/* Length (bytes) of messaging part of shared memory */
|
|
msg_sz = (s->msg_lim - s->msg_base + 1) * hio_mgr->word_size;
|
|
|
|
/* Total length (bytes) of shared memory: chnl + msg */
|
|
mem_sz = shm_sz + msg_sz;
|
|
|
|
/* Get memory reserved in host resources */
|
|
(void)mgr_enum_processor_info(0,
|
|
(struct dsp_processorinfo *)
|
|
&hio_mgr->ext_proc_info,
|
|
sizeof(struct mgr_processorextinfo),
|
|
&num_procs);
|
|
|
|
/* IO supports only one DSP for now */
|
|
if (num_procs != 1) {
|
|
status = -EINVAL;
|
|
goto free_symbol;
|
|
}
|
|
|
|
/* The first MMU TLB entry(TLB_0) in DCD is ShmBase */
|
|
pa = cfg_res->mem_phys[1];
|
|
va = cfg_res->mem_base[1];
|
|
|
|
/* This is the virtual uncached ioremapped address!!! */
|
|
/* Why can't we directly take the DSPVA from the symbols? */
|
|
da = hio_mgr->ext_proc_info.ty_tlb[0].dsp_virt;
|
|
seg0_sz = (s->shm0_end - da) * hio_mgr->word_size;
|
|
seg1_sz = (s->ext_end - s->dyn_ext) * hio_mgr->word_size;
|
|
|
|
/* 4K align */
|
|
seg1_sz = (seg1_sz + 0xFFF) & (~0xFFFUL);
|
|
|
|
/* 64K align */
|
|
seg0_sz = (seg0_sz + 0xFFFF) & (~0xFFFFUL);
|
|
|
|
pad_sz = UL_PAGE_ALIGN_SIZE - ((pa + seg1_sz) % UL_PAGE_ALIGN_SIZE);
|
|
if (pad_sz == UL_PAGE_ALIGN_SIZE)
|
|
pad_sz = 0x0;
|
|
|
|
dev_dbg(bridge, "%s: pa %x, va %x, da %x\n", __func__, pa, va, da);
|
|
dev_dbg(bridge,
|
|
"shm0_end %x, dyn_ext %x, ext_end %x, seg0_sz %x seg1_sz %x\n",
|
|
s->shm0_end, s->dyn_ext, s->ext_end, seg0_sz, seg1_sz);
|
|
|
|
if ((seg0_sz + seg1_sz + pad_sz) > cfg_res->mem_length[1]) {
|
|
pr_err("%s: shm Error, reserved 0x%x required 0x%x\n",
|
|
__func__, cfg_res->mem_length[1],
|
|
seg0_sz + seg1_sz + pad_sz);
|
|
status = -ENOMEM;
|
|
goto free_symbol;
|
|
}
|
|
|
|
pa_curr = pa;
|
|
va_curr = s->dyn_ext * hio_mgr->word_size;
|
|
da_curr = va;
|
|
bytes = seg1_sz;
|
|
|
|
/*
|
|
* Try to fit into TLB entries. If not possible, push them to page
|
|
* tables. It is quite possible that if sections are not on
|
|
* bigger page boundary, we may end up making several small pages.
|
|
* So, push them onto page tables, if that is the case.
|
|
*/
|
|
while (bytes) {
|
|
/*
|
|
* To find the max. page size with which both PA & VA are
|
|
* aligned.
|
|
*/
|
|
all_bits = pa_curr | va_curr;
|
|
dev_dbg(bridge,
|
|
"seg all_bits %x, pa_curr %x, va_curr %x, bytes %x\n",
|
|
all_bits, pa_curr, va_curr, bytes);
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if ((bytes >= page_size[i]) &&
|
|
((all_bits & (page_size[i] - 1)) == 0)) {
|
|
status = hio_mgr->intf_fxns->brd_mem_map(dc,
|
|
pa_curr, va_curr,
|
|
page_size[i], map_attrs,
|
|
NULL);
|
|
if (status)
|
|
goto free_symbol;
|
|
|
|
pa_curr += page_size[i];
|
|
va_curr += page_size[i];
|
|
da_curr += page_size[i];
|
|
bytes -= page_size[i];
|
|
/*
|
|
* Don't try smaller sizes. Hopefully we have
|
|
* reached an address aligned to a bigger page
|
|
* size.
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pa_curr += pad_sz;
|
|
va_curr += pad_sz;
|
|
da_curr += pad_sz;
|
|
bytes = seg0_sz;
|
|
va_curr = da * hio_mgr->word_size;
|
|
|
|
eproc = kzalloc(sizeof(*eproc) * BRDIOCTL_NUMOFMMUTLB, GFP_KERNEL);
|
|
if (!eproc) {
|
|
status = -ENOMEM;
|
|
goto free_symbol;
|
|
}
|
|
|
|
ndx = 0;
|
|
/* Configure the TLB entries for the next cacheable segment */
|
|
while (bytes) {
|
|
/*
|
|
* To find the max. page size with which both PA & VA are
|
|
* aligned.
|
|
*/
|
|
all_bits = pa_curr | va_curr;
|
|
dev_dbg(bridge,
|
|
"seg1 all_bits %x, pa_curr %x, va_curr %x, bytes %x\n",
|
|
all_bits, pa_curr, va_curr, bytes);
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if (!(bytes >= page_size[i]) ||
|
|
!((all_bits & (page_size[i] - 1)) == 0))
|
|
continue;
|
|
|
|
if (ndx >= MAX_LOCK_TLB_ENTRIES) {
|
|
status = hio_mgr->intf_fxns->brd_mem_map(dc,
|
|
pa_curr, va_curr,
|
|
page_size[i], map_attrs,
|
|
NULL);
|
|
dev_dbg(bridge,
|
|
"PTE pa %x va %x dsp_va %x sz %x\n",
|
|
eproc[ndx].gpp_pa,
|
|
eproc[ndx].gpp_va,
|
|
eproc[ndx].dsp_va *
|
|
hio_mgr->word_size, page_size[i]);
|
|
if (status)
|
|
goto free_eproc;
|
|
}
|
|
|
|
/* This is the physical address written to DSP MMU */
|
|
eproc[ndx].gpp_pa = pa_curr;
|
|
|
|
/*
|
|
* This is the virtual uncached ioremapped
|
|
* address!!!
|
|
*/
|
|
eproc[ndx].gpp_va = da_curr;
|
|
eproc[ndx].dsp_va = va_curr / hio_mgr->word_size;
|
|
eproc[ndx].size = page_size[i];
|
|
eproc[ndx].endianism = HW_LITTLE_ENDIAN;
|
|
eproc[ndx].elem_size = HW_ELEM_SIZE16BIT;
|
|
eproc[ndx].mixed_mode = HW_MMU_CPUES;
|
|
dev_dbg(bridge, "%s: tlb pa %x va %x dsp_va %x sz %x\n",
|
|
__func__, eproc[ndx].gpp_pa,
|
|
eproc[ndx].gpp_va,
|
|
eproc[ndx].dsp_va * hio_mgr->word_size,
|
|
page_size[i]);
|
|
ndx++;
|
|
|
|
pa_curr += page_size[i];
|
|
va_curr += page_size[i];
|
|
da_curr += page_size[i];
|
|
bytes -= page_size[i];
|
|
/*
|
|
* Don't try smaller sizes. Hopefully we have reached
|
|
* an address aligned to a bigger page size.
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copy remaining entries from CDB. All entries are 1 MB and
|
|
* should not conflict with shm entries on MPU or DSP side.
|
|
*/
|
|
for (i = 3; i < 7 && ndx < BRDIOCTL_NUMOFMMUTLB; i++) {
|
|
struct mgr_processorextinfo *ep = &hio_mgr->ext_proc_info;
|
|
u32 word_sz = hio_mgr->word_size;
|
|
|
|
if (ep->ty_tlb[i].gpp_phys == 0)
|
|
continue;
|
|
|
|
if ((ep->ty_tlb[i].gpp_phys > pa - 0x100000 &&
|
|
ep->ty_tlb[i].gpp_phys <= pa + seg0_sz) ||
|
|
(ep->ty_tlb[i].dsp_virt > da - 0x100000 / word_sz &&
|
|
ep->ty_tlb[i].dsp_virt <= da + seg0_sz / word_sz)) {
|
|
dev_dbg(bridge,
|
|
"err cdb%d pa %x da %x shm pa %x da %x sz %x\n",
|
|
i, ep->ty_tlb[i].gpp_phys,
|
|
ep->ty_tlb[i].dsp_virt, pa, da, seg0_sz);
|
|
status = -EPERM;
|
|
goto free_eproc;
|
|
}
|
|
|
|
if (ndx >= MAX_LOCK_TLB_ENTRIES) {
|
|
status = hio_mgr->intf_fxns->brd_mem_map(dc,
|
|
ep->ty_tlb[i].gpp_phys,
|
|
ep->ty_tlb[i].dsp_virt,
|
|
0x100000, map_attrs, NULL);
|
|
if (status)
|
|
goto free_eproc;
|
|
}
|
|
|
|
eproc[ndx].dsp_va = ep->ty_tlb[i].dsp_virt;
|
|
eproc[ndx].gpp_pa = ep->ty_tlb[i].gpp_phys;
|
|
eproc[ndx].gpp_va = 0;
|
|
|
|
/* 1 MB */
|
|
eproc[ndx].size = 0x100000;
|
|
dev_dbg(bridge, "shm MMU entry pa %x da 0x%x\n",
|
|
eproc[ndx].gpp_pa, eproc[ndx].dsp_va);
|
|
ndx++;
|
|
}
|
|
|
|
/* Map the L4 peripherals */
|
|
i = 0;
|
|
while (l4_peripheral_table[i].phys_addr) {
|
|
status = hio_mgr->intf_fxns->brd_mem_map(dc,
|
|
l4_peripheral_table[i].phys_addr,
|
|
l4_peripheral_table[i].dsp_virt_addr,
|
|
HW_PAGE_SIZE4KB, map_attrs, NULL);
|
|
if (status)
|
|
goto free_eproc;
|
|
i++;
|
|
}
|
|
|
|
for (i = ndx; i < BRDIOCTL_NUMOFMMUTLB; i++) {
|
|
eproc[i].dsp_va = 0;
|
|
eproc[i].gpp_pa = 0;
|
|
eproc[i].gpp_va = 0;
|
|
eproc[i].size = 0;
|
|
}
|
|
|
|
/*
|
|
* Set the shm physical address entry (grayed out in CDB file)
|
|
* to the virtual uncached ioremapped address of shm reserved
|
|
* on MPU.
|
|
*/
|
|
hio_mgr->ext_proc_info.ty_tlb[0].gpp_phys =
|
|
(va + seg1_sz + pad_sz);
|
|
|
|
/*
|
|
* Need shm Phys addr. IO supports only one DSP for now:
|
|
* num_procs = 1.
|
|
*/
|
|
if (!hio_mgr->ext_proc_info.ty_tlb[0].gpp_phys)
|
|
return -EFAULT;
|
|
|
|
if (eproc[0].dsp_va > s->shm_base)
|
|
return -EPERM;
|
|
|
|
/* shm_base may not be at ul_dsp_va address */
|
|
shm_base_offs = (s->shm_base - eproc[0].dsp_va) *
|
|
hio_mgr->word_size;
|
|
/*
|
|
* bridge_dev_ctrl() will set dev context dsp-mmu info. In
|
|
* bridge_brd_start() the MMU will be re-programed with MMU
|
|
* DSPVa-GPPPa pair info while DSP is in a known
|
|
* (reset) state.
|
|
*/
|
|
status = hio_mgr->intf_fxns->dev_cntrl(hio_mgr->bridge_context,
|
|
BRDIOCTL_SETMMUCONFIG, eproc);
|
|
if (status)
|
|
goto free_eproc;
|
|
|
|
s->shm_base = hio_mgr->ext_proc_info.ty_tlb[0].gpp_phys;
|
|
s->shm_base += shm_base_offs;
|
|
s->shm_base = (u32) MEM_LINEAR_ADDRESS((void *)s->shm_base,
|
|
mem_sz);
|
|
if (!s->shm_base) {
|
|
status = -EFAULT;
|
|
goto free_eproc;
|
|
}
|
|
|
|
/* Register SM */
|
|
status = register_shm_segs(hio_mgr, cod_man, eproc[0].gpp_pa);
|
|
|
|
hio_mgr->shared_mem = (struct shm *)s->shm_base;
|
|
hio_mgr->input = (u8 *) hio_mgr->shared_mem + sizeof(struct shm);
|
|
hio_mgr->output = hio_mgr->input + (shm_sz -
|
|
sizeof(struct shm)) / 2;
|
|
hio_mgr->sm_buf_size = hio_mgr->output - hio_mgr->input;
|
|
|
|
/* Set up Shared memory addresses for messaging */
|
|
hio_mgr->msg_input_ctrl =
|
|
(struct msg_ctrl *)((u8 *) hio_mgr->shared_mem + shm_sz);
|
|
hio_mgr->msg_input =
|
|
(u8 *) hio_mgr->msg_input_ctrl + sizeof(struct msg_ctrl);
|
|
hio_mgr->msg_output_ctrl =
|
|
(struct msg_ctrl *)((u8 *) hio_mgr->msg_input_ctrl +
|
|
msg_sz / 2);
|
|
hio_mgr->msg_output =
|
|
(u8 *) hio_mgr->msg_output_ctrl + sizeof(struct msg_ctrl);
|
|
hmsg_mgr->max_msgs =
|
|
((u8 *) hio_mgr->msg_output_ctrl - hio_mgr->msg_input) /
|
|
sizeof(struct msg_dspmsg);
|
|
|
|
dev_dbg(bridge, "IO MGR shm details: shared_mem %p, input %p, "
|
|
"output %p, msg_input_ctrl %p, msg_input %p, "
|
|
"msg_output_ctrl %p, msg_output %p\n",
|
|
(u8 *) hio_mgr->shared_mem, hio_mgr->input,
|
|
hio_mgr->output, (u8 *) hio_mgr->msg_input_ctrl,
|
|
hio_mgr->msg_input, (u8 *) hio_mgr->msg_output_ctrl,
|
|
hio_mgr->msg_output);
|
|
dev_dbg(bridge, "(proc) Mas msgs in shared memory: 0x%x\n",
|
|
hmsg_mgr->max_msgs);
|
|
memset((void *)hio_mgr->shared_mem, 0, sizeof(struct shm));
|
|
|
|
#if defined(CONFIG_TIDSPBRIDGE_BACKTRACE)
|
|
/* Get the start address of trace buffer */
|
|
status = cod_get_sym_value(cod_man, SYS_PUTCBEG,
|
|
&hio_mgr->trace_buffer_begin);
|
|
if (status)
|
|
goto free_eproc;
|
|
|
|
hio_mgr->gpp_read_pointer =
|
|
hio_mgr->trace_buffer_begin =
|
|
(va + seg1_sz + pad_sz) +
|
|
(hio_mgr->trace_buffer_begin - da);
|
|
|
|
/* Get the end address of trace buffer */
|
|
status = cod_get_sym_value(cod_man, SYS_PUTCEND,
|
|
&hio_mgr->trace_buffer_end);
|
|
if (status)
|
|
goto free_eproc;
|
|
|
|
hio_mgr->trace_buffer_end =
|
|
(va + seg1_sz + pad_sz) +
|
|
(hio_mgr->trace_buffer_end - da);
|
|
|
|
/* Get the current address of DSP write pointer */
|
|
status = cod_get_sym_value(cod_man, BRIDGE_SYS_PUTC_CURRENT,
|
|
&hio_mgr->trace_buffer_current);
|
|
if (status)
|
|
goto free_eproc;
|
|
|
|
hio_mgr->trace_buffer_current =
|
|
(va + seg1_sz + pad_sz) +
|
|
(hio_mgr->trace_buffer_current - da);
|
|
|
|
/* Calculate the size of trace buffer */
|
|
kfree(hio_mgr->msg);
|
|
hio_mgr->msg = kmalloc(((hio_mgr->trace_buffer_end -
|
|
hio_mgr->trace_buffer_begin) *
|
|
hio_mgr->word_size) + 2, GFP_KERNEL);
|
|
if (!hio_mgr->msg) {
|
|
status = -ENOMEM;
|
|
goto free_eproc;
|
|
}
|
|
|
|
hio_mgr->dsp_va = da;
|
|
hio_mgr->gpp_va = (va + seg1_sz + pad_sz);
|
|
#endif
|
|
|
|
free_eproc:
|
|
kfree(eproc);
|
|
free_symbol:
|
|
kfree(s);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* ======== io_buf_size ========
|
|
* Size of shared memory I/O channel.
|
|
*/
|
|
u32 io_buf_size(struct io_mgr *hio_mgr)
|
|
{
|
|
if (hio_mgr)
|
|
return hio_mgr->sm_buf_size;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ======== io_cancel_chnl ========
|
|
* Cancel IO on a given PCPY channel.
|
|
*/
|
|
void io_cancel_chnl(struct io_mgr *hio_mgr, u32 chnl)
|
|
{
|
|
struct io_mgr *pio_mgr = (struct io_mgr *)hio_mgr;
|
|
struct shm *sm;
|
|
|
|
if (!hio_mgr)
|
|
goto func_end;
|
|
sm = hio_mgr->shared_mem;
|
|
|
|
/* Inform DSP that we have no more buffers on this channel */
|
|
set_chnl_free(sm, chnl);
|
|
|
|
sm_interrupt_dsp(pio_mgr->bridge_context, MBX_PCPY_CLASS);
|
|
func_end:
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* ======== io_dispatch_pm ========
|
|
* Performs I/O dispatch on PM related messages from DSP
|
|
*/
|
|
static void io_dispatch_pm(struct io_mgr *pio_mgr)
|
|
{
|
|
int status;
|
|
u32 parg[2];
|
|
|
|
/* Perform Power message processing here */
|
|
parg[0] = pio_mgr->intr_val;
|
|
|
|
/* Send the command to the Bridge clk/pwr manager to handle */
|
|
if (parg[0] == MBX_PM_HIBERNATE_EN) {
|
|
dev_dbg(bridge, "PM: Hibernate command\n");
|
|
status = pio_mgr->intf_fxns->
|
|
dev_cntrl(pio_mgr->bridge_context,
|
|
BRDIOCTL_PWR_HIBERNATE, parg);
|
|
if (status)
|
|
pr_err("%s: hibernate cmd failed 0x%x\n",
|
|
__func__, status);
|
|
} else if (parg[0] == MBX_PM_OPP_REQ) {
|
|
parg[1] = pio_mgr->shared_mem->opp_request.rqst_opp_pt;
|
|
dev_dbg(bridge, "PM: Requested OPP = 0x%x\n", parg[1]);
|
|
status = pio_mgr->intf_fxns->
|
|
dev_cntrl(pio_mgr->bridge_context,
|
|
BRDIOCTL_CONSTRAINT_REQUEST, parg);
|
|
if (status)
|
|
dev_dbg(bridge, "PM: Failed to set constraint "
|
|
"= 0x%x\n", parg[1]);
|
|
} else {
|
|
dev_dbg(bridge, "PM: clk control value of msg = 0x%x\n",
|
|
parg[0]);
|
|
status = pio_mgr->intf_fxns->
|
|
dev_cntrl(pio_mgr->bridge_context,
|
|
BRDIOCTL_CLK_CTRL, parg);
|
|
if (status)
|
|
dev_dbg(bridge, "PM: Failed to ctrl the DSP clk"
|
|
"= 0x%x\n", *parg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ======== io_dpc ========
|
|
* Deferred procedure call for shared memory channel driver ISR. Carries
|
|
* out the dispatch of I/O as a non-preemptible event. It can only be
|
|
* pre-empted by an ISR.
|
|
*/
|
|
void io_dpc(unsigned long ref_data)
|
|
{
|
|
struct io_mgr *pio_mgr = (struct io_mgr *)ref_data;
|
|
struct chnl_mgr *chnl_mgr_obj;
|
|
struct msg_mgr *msg_mgr_obj;
|
|
struct deh_mgr *hdeh_mgr;
|
|
u32 requested;
|
|
u32 serviced;
|
|
|
|
if (!pio_mgr)
|
|
goto func_end;
|
|
chnl_mgr_obj = pio_mgr->chnl_mgr;
|
|
dev_get_msg_mgr(pio_mgr->dev_obj, &msg_mgr_obj);
|
|
dev_get_deh_mgr(pio_mgr->dev_obj, &hdeh_mgr);
|
|
if (!chnl_mgr_obj)
|
|
goto func_end;
|
|
|
|
requested = pio_mgr->dpc_req;
|
|
serviced = pio_mgr->dpc_sched;
|
|
|
|
if (serviced == requested)
|
|
goto func_end;
|
|
|
|
/* Process pending DPC's */
|
|
do {
|
|
/* Check value of interrupt reg to ensure it's a valid error */
|
|
if ((pio_mgr->intr_val > DEH_BASE) &&
|
|
(pio_mgr->intr_val < DEH_LIMIT)) {
|
|
/* Notify DSP/BIOS exception */
|
|
if (hdeh_mgr) {
|
|
#ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
|
|
print_dsp_debug_trace(pio_mgr);
|
|
#endif
|
|
bridge_deh_notify(hdeh_mgr, DSP_SYSERROR,
|
|
pio_mgr->intr_val);
|
|
}
|
|
}
|
|
/* Proc-copy channel dispatch */
|
|
input_chnl(pio_mgr, NULL, IO_SERVICE);
|
|
output_chnl(pio_mgr, NULL, IO_SERVICE);
|
|
|
|
#ifdef CHNL_MESSAGES
|
|
if (msg_mgr_obj) {
|
|
/* Perform I/O dispatch on message queues */
|
|
input_msg(pio_mgr, msg_mgr_obj);
|
|
output_msg(pio_mgr, msg_mgr_obj);
|
|
}
|
|
|
|
#endif
|
|
#ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
|
|
if (pio_mgr->intr_val & MBX_DBG_SYSPRINTF) {
|
|
/* Notify DSP Trace message */
|
|
print_dsp_debug_trace(pio_mgr);
|
|
}
|
|
#endif
|
|
serviced++;
|
|
} while (serviced != requested);
|
|
pio_mgr->dpc_sched = requested;
|
|
func_end:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* ======== io_mbox_msg ========
|
|
* Main interrupt handler for the shared memory IO manager.
|
|
* Calls the Bridge's CHNL_ISR to determine if this interrupt is ours, then
|
|
* schedules a DPC to dispatch I/O.
|
|
*/
|
|
int io_mbox_msg(struct notifier_block *self, unsigned long len, void *msg)
|
|
{
|
|
struct io_mgr *pio_mgr;
|
|
struct dev_object *dev_obj;
|
|
unsigned long flags;
|
|
|
|
dev_obj = dev_get_first();
|
|
dev_get_io_mgr(dev_obj, &pio_mgr);
|
|
|
|
if (!pio_mgr)
|
|
return NOTIFY_BAD;
|
|
|
|
pio_mgr->intr_val = (u16)((u32)msg);
|
|
if (pio_mgr->intr_val & MBX_PM_CLASS)
|
|
io_dispatch_pm(pio_mgr);
|
|
|
|
if (pio_mgr->intr_val == MBX_DEH_RESET) {
|
|
pio_mgr->intr_val = 0;
|
|
} else {
|
|
spin_lock_irqsave(&pio_mgr->dpc_lock, flags);
|
|
pio_mgr->dpc_req++;
|
|
spin_unlock_irqrestore(&pio_mgr->dpc_lock, flags);
|
|
tasklet_schedule(&pio_mgr->dpc_tasklet);
|
|
}
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
/*
|
|
* ======== io_request_chnl ========
|
|
* Purpose:
|
|
* Request channel I/O from the DSP. Sets flags in shared memory, then
|
|
* interrupts the DSP.
|
|
*/
|
|
void io_request_chnl(struct io_mgr *io_manager, struct chnl_object *pchnl,
|
|
u8 io_mode, u16 *mbx_val)
|
|
{
|
|
struct chnl_mgr *chnl_mgr_obj;
|
|
struct shm *sm;
|
|
|
|
if (!pchnl || !mbx_val)
|
|
goto func_end;
|
|
chnl_mgr_obj = io_manager->chnl_mgr;
|
|
sm = io_manager->shared_mem;
|
|
if (io_mode == IO_INPUT) {
|
|
/* Indicate to the DSP we have a buffer available for input */
|
|
set_chnl_busy(sm, pchnl->chnl_id);
|
|
*mbx_val = MBX_PCPY_CLASS;
|
|
} else if (io_mode == IO_OUTPUT) {
|
|
/*
|
|
* Record the fact that we have a buffer available for
|
|
* output.
|
|
*/
|
|
chnl_mgr_obj->output_mask |= (1 << pchnl->chnl_id);
|
|
} else {
|
|
}
|
|
func_end:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* ======== iosm_schedule ========
|
|
* Schedule DPC for IO.
|
|
*/
|
|
void iosm_schedule(struct io_mgr *io_manager)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (!io_manager)
|
|
return;
|
|
|
|
/* Increment count of DPC's pending. */
|
|
spin_lock_irqsave(&io_manager->dpc_lock, flags);
|
|
io_manager->dpc_req++;
|
|
spin_unlock_irqrestore(&io_manager->dpc_lock, flags);
|
|
|
|
/* Schedule DPC */
|
|
tasklet_schedule(&io_manager->dpc_tasklet);
|
|
}
|
|
|
|
/*
|
|
* ======== find_ready_output ========
|
|
* Search for a host output channel which is ready to send. If this is
|
|
* called as a result of servicing the DPC, then implement a round
|
|
* robin search; otherwise, this was called by a client thread (via
|
|
* IO_Dispatch()), so just start searching from the current channel id.
|
|
*/
|
|
static u32 find_ready_output(struct chnl_mgr *chnl_mgr_obj,
|
|
struct chnl_object *pchnl, u32 mask)
|
|
{
|
|
u32 ret = OUTPUTNOTREADY;
|
|
u32 id, start_id;
|
|
u32 shift;
|
|
|
|
id = (pchnl !=
|
|
NULL ? pchnl->chnl_id : (chnl_mgr_obj->last_output + 1));
|
|
id = ((id == CHNL_MAXCHANNELS) ? 0 : id);
|
|
if (id >= CHNL_MAXCHANNELS)
|
|
goto func_end;
|
|
if (mask) {
|
|
shift = (1 << id);
|
|
start_id = id;
|
|
do {
|
|
if (mask & shift) {
|
|
ret = id;
|
|
if (pchnl == NULL)
|
|
chnl_mgr_obj->last_output = id;
|
|
break;
|
|
}
|
|
id = id + 1;
|
|
id = ((id == CHNL_MAXCHANNELS) ? 0 : id);
|
|
shift = (1 << id);
|
|
} while (id != start_id);
|
|
}
|
|
func_end:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* ======== input_chnl ========
|
|
* Dispatch a buffer on an input channel.
|
|
*/
|
|
static void input_chnl(struct io_mgr *pio_mgr, struct chnl_object *pchnl,
|
|
u8 io_mode)
|
|
{
|
|
struct chnl_mgr *chnl_mgr_obj;
|
|
struct shm *sm;
|
|
u32 chnl_id;
|
|
u32 bytes;
|
|
struct chnl_irp *chnl_packet_obj = NULL;
|
|
u32 dw_arg;
|
|
bool clear_chnl = false;
|
|
bool notify_client = false;
|
|
|
|
sm = pio_mgr->shared_mem;
|
|
chnl_mgr_obj = pio_mgr->chnl_mgr;
|
|
|
|
/* Attempt to perform input */
|
|
if (!sm->input_full)
|
|
goto func_end;
|
|
|
|
bytes = sm->input_size * chnl_mgr_obj->word_size;
|
|
chnl_id = sm->input_id;
|
|
dw_arg = sm->arg;
|
|
if (chnl_id >= CHNL_MAXCHANNELS) {
|
|
/* Shouldn't be here: would indicate corrupted shm. */
|
|
goto func_end;
|
|
}
|
|
pchnl = chnl_mgr_obj->channels[chnl_id];
|
|
if ((pchnl != NULL) && CHNL_IS_INPUT(pchnl->chnl_mode)) {
|
|
if ((pchnl->state & ~CHNL_STATEEOS) == CHNL_STATEREADY) {
|
|
/* Get the I/O request, and attempt a transfer */
|
|
if (!list_empty(&pchnl->io_requests)) {
|
|
if (!pchnl->cio_reqs)
|
|
goto func_end;
|
|
|
|
chnl_packet_obj = list_first_entry(
|
|
&pchnl->io_requests,
|
|
struct chnl_irp, link);
|
|
list_del(&chnl_packet_obj->link);
|
|
pchnl->cio_reqs--;
|
|
|
|
/*
|
|
* Ensure we don't overflow the client's
|
|
* buffer.
|
|
*/
|
|
bytes = min(bytes, chnl_packet_obj->byte_size);
|
|
memcpy(chnl_packet_obj->host_sys_buf,
|
|
pio_mgr->input, bytes);
|
|
pchnl->bytes_moved += bytes;
|
|
chnl_packet_obj->byte_size = bytes;
|
|
chnl_packet_obj->arg = dw_arg;
|
|
chnl_packet_obj->status = CHNL_IOCSTATCOMPLETE;
|
|
|
|
if (bytes == 0) {
|
|
/*
|
|
* This assertion fails if the DSP
|
|
* sends EOS more than once on this
|
|
* channel.
|
|
*/
|
|
if (pchnl->state & CHNL_STATEEOS)
|
|
goto func_end;
|
|
/*
|
|
* Zero bytes indicates EOS. Update
|
|
* IOC status for this chirp, and also
|
|
* the channel state.
|
|
*/
|
|
chnl_packet_obj->status |=
|
|
CHNL_IOCSTATEOS;
|
|
pchnl->state |= CHNL_STATEEOS;
|
|
/*
|
|
* Notify that end of stream has
|
|
* occurred.
|
|
*/
|
|
ntfy_notify(pchnl->ntfy_obj,
|
|
DSP_STREAMDONE);
|
|
}
|
|
/* Tell DSP if no more I/O buffers available */
|
|
if (list_empty(&pchnl->io_requests))
|
|
set_chnl_free(sm, pchnl->chnl_id);
|
|
clear_chnl = true;
|
|
notify_client = true;
|
|
} else {
|
|
/*
|
|
* Input full for this channel, but we have no
|
|
* buffers available. The channel must be
|
|
* "idling". Clear out the physical input
|
|
* channel.
|
|
*/
|
|
clear_chnl = true;
|
|
}
|
|
} else {
|
|
/* Input channel cancelled: clear input channel */
|
|
clear_chnl = true;
|
|
}
|
|
} else {
|
|
/* DPC fired after host closed channel: clear input channel */
|
|
clear_chnl = true;
|
|
}
|
|
if (clear_chnl) {
|
|
/* Indicate to the DSP we have read the input */
|
|
sm->input_full = 0;
|
|
sm_interrupt_dsp(pio_mgr->bridge_context, MBX_PCPY_CLASS);
|
|
}
|
|
if (notify_client) {
|
|
/* Notify client with IO completion record */
|
|
notify_chnl_complete(pchnl, chnl_packet_obj);
|
|
}
|
|
func_end:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* ======== input_msg ========
|
|
* Copies messages from shared memory to the message queues.
|
|
*/
|
|
static void input_msg(struct io_mgr *pio_mgr, struct msg_mgr *hmsg_mgr)
|
|
{
|
|
u32 num_msgs;
|
|
u32 i;
|
|
u8 *msg_input;
|
|
struct msg_queue *msg_queue_obj;
|
|
struct msg_frame *pmsg;
|
|
struct msg_dspmsg msg;
|
|
struct msg_ctrl *msg_ctr_obj;
|
|
u32 input_empty;
|
|
u32 addr;
|
|
|
|
msg_ctr_obj = pio_mgr->msg_input_ctrl;
|
|
/* Get the number of input messages to be read */
|
|
input_empty = msg_ctr_obj->buf_empty;
|
|
num_msgs = msg_ctr_obj->size;
|
|
if (input_empty)
|
|
return;
|
|
|
|
msg_input = pio_mgr->msg_input;
|
|
for (i = 0; i < num_msgs; i++) {
|
|
/* Read the next message */
|
|
addr = (u32) &(((struct msg_dspmsg *)msg_input)->msg.cmd);
|
|
msg.msg.cmd =
|
|
read_ext32_bit_dsp_data(pio_mgr->bridge_context, addr);
|
|
addr = (u32) &(((struct msg_dspmsg *)msg_input)->msg.arg1);
|
|
msg.msg.arg1 =
|
|
read_ext32_bit_dsp_data(pio_mgr->bridge_context, addr);
|
|
addr = (u32) &(((struct msg_dspmsg *)msg_input)->msg.arg2);
|
|
msg.msg.arg2 =
|
|
read_ext32_bit_dsp_data(pio_mgr->bridge_context, addr);
|
|
addr = (u32) &(((struct msg_dspmsg *)msg_input)->msgq_id);
|
|
msg.msgq_id =
|
|
read_ext32_bit_dsp_data(pio_mgr->bridge_context, addr);
|
|
msg_input += sizeof(struct msg_dspmsg);
|
|
|
|
/* Determine which queue to put the message in */
|
|
dev_dbg(bridge, "input msg: cmd=0x%x arg1=0x%x "
|
|
"arg2=0x%x msgq_id=0x%x\n", msg.msg.cmd,
|
|
msg.msg.arg1, msg.msg.arg2, msg.msgq_id);
|
|
/*
|
|
* Interrupt may occur before shared memory and message
|
|
* input locations have been set up. If all nodes were
|
|
* cleaned up, hmsg_mgr->max_msgs should be 0.
|
|
*/
|
|
list_for_each_entry(msg_queue_obj, &hmsg_mgr->queue_list,
|
|
list_elem) {
|
|
if (msg.msgq_id != msg_queue_obj->msgq_id)
|
|
continue;
|
|
/* Found it */
|
|
if (msg.msg.cmd == RMS_EXITACK) {
|
|
/*
|
|
* Call the node exit notification.
|
|
* The exit message does not get
|
|
* queued.
|
|
*/
|
|
(*hmsg_mgr->on_exit)(msg_queue_obj->arg,
|
|
msg.msg.arg1);
|
|
break;
|
|
}
|
|
/*
|
|
* Not an exit acknowledgement, queue
|
|
* the message.
|
|
*/
|
|
if (list_empty(&msg_queue_obj->msg_free_list)) {
|
|
/*
|
|
* No free frame to copy the
|
|
* message into.
|
|
*/
|
|
pr_err("%s: no free msg frames,"
|
|
" discarding msg\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
pmsg = list_first_entry(&msg_queue_obj->msg_free_list,
|
|
struct msg_frame, list_elem);
|
|
list_del(&pmsg->list_elem);
|
|
pmsg->msg_data = msg;
|
|
list_add_tail(&pmsg->list_elem,
|
|
&msg_queue_obj->msg_used_list);
|
|
ntfy_notify(msg_queue_obj->ntfy_obj,
|
|
DSP_NODEMESSAGEREADY);
|
|
sync_set_event(msg_queue_obj->sync_event);
|
|
}
|
|
}
|
|
/* Set the post SWI flag */
|
|
if (num_msgs > 0) {
|
|
/* Tell the DSP we've read the messages */
|
|
msg_ctr_obj->buf_empty = true;
|
|
msg_ctr_obj->post_swi = true;
|
|
sm_interrupt_dsp(pio_mgr->bridge_context, MBX_PCPY_CLASS);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ======== notify_chnl_complete ========
|
|
* Purpose:
|
|
* Signal the channel event, notifying the client that I/O has completed.
|
|
*/
|
|
static void notify_chnl_complete(struct chnl_object *pchnl,
|
|
struct chnl_irp *chnl_packet_obj)
|
|
{
|
|
bool signal_event;
|
|
|
|
if (!pchnl || !pchnl->sync_event || !chnl_packet_obj)
|
|
goto func_end;
|
|
|
|
/*
|
|
* Note: we signal the channel event only if the queue of IO
|
|
* completions is empty. If it is not empty, the event is sure to be
|
|
* signalled by the only IO completion list consumer:
|
|
* bridge_chnl_get_ioc().
|
|
*/
|
|
signal_event = list_empty(&pchnl->io_completions);
|
|
/* Enqueue the IO completion info for the client */
|
|
list_add_tail(&chnl_packet_obj->link, &pchnl->io_completions);
|
|
pchnl->cio_cs++;
|
|
|
|
if (pchnl->cio_cs > pchnl->chnl_packets)
|
|
goto func_end;
|
|
/* Signal the channel event (if not already set) that IO is complete */
|
|
if (signal_event)
|
|
sync_set_event(pchnl->sync_event);
|
|
|
|
/* Notify that IO is complete */
|
|
ntfy_notify(pchnl->ntfy_obj, DSP_STREAMIOCOMPLETION);
|
|
func_end:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* ======== output_chnl ========
|
|
* Purpose:
|
|
* Dispatch a buffer on an output channel.
|
|
*/
|
|
static void output_chnl(struct io_mgr *pio_mgr, struct chnl_object *pchnl,
|
|
u8 io_mode)
|
|
{
|
|
struct chnl_mgr *chnl_mgr_obj;
|
|
struct shm *sm;
|
|
u32 chnl_id;
|
|
struct chnl_irp *chnl_packet_obj;
|
|
u32 dw_dsp_f_mask;
|
|
|
|
chnl_mgr_obj = pio_mgr->chnl_mgr;
|
|
sm = pio_mgr->shared_mem;
|
|
/* Attempt to perform output */
|
|
if (sm->output_full)
|
|
goto func_end;
|
|
|
|
if (pchnl && !((pchnl->state & ~CHNL_STATEEOS) == CHNL_STATEREADY))
|
|
goto func_end;
|
|
|
|
/* Look to see if both a PC and DSP output channel are ready */
|
|
dw_dsp_f_mask = sm->dsp_free_mask;
|
|
chnl_id =
|
|
find_ready_output(chnl_mgr_obj, pchnl,
|
|
(chnl_mgr_obj->output_mask & dw_dsp_f_mask));
|
|
if (chnl_id == OUTPUTNOTREADY)
|
|
goto func_end;
|
|
|
|
pchnl = chnl_mgr_obj->channels[chnl_id];
|
|
if (!pchnl || list_empty(&pchnl->io_requests)) {
|
|
/* Shouldn't get here */
|
|
goto func_end;
|
|
}
|
|
|
|
if (!pchnl->cio_reqs)
|
|
goto func_end;
|
|
|
|
/* Get the I/O request, and attempt a transfer */
|
|
chnl_packet_obj = list_first_entry(&pchnl->io_requests,
|
|
struct chnl_irp, link);
|
|
list_del(&chnl_packet_obj->link);
|
|
|
|
pchnl->cio_reqs--;
|
|
|
|
/* Record fact that no more I/O buffers available */
|
|
if (list_empty(&pchnl->io_requests))
|
|
chnl_mgr_obj->output_mask &= ~(1 << chnl_id);
|
|
|
|
/* Transfer buffer to DSP side */
|
|
chnl_packet_obj->byte_size = min(pio_mgr->sm_buf_size,
|
|
chnl_packet_obj->byte_size);
|
|
memcpy(pio_mgr->output, chnl_packet_obj->host_sys_buf,
|
|
chnl_packet_obj->byte_size);
|
|
pchnl->bytes_moved += chnl_packet_obj->byte_size;
|
|
/* Write all 32 bits of arg */
|
|
sm->arg = chnl_packet_obj->arg;
|
|
#if _CHNL_WORDSIZE == 2
|
|
/* Access can be different SM access word size (e.g. 16/32 bit words) */
|
|
sm->output_id = (u16) chnl_id;
|
|
sm->output_size = (u16) (chnl_packet_obj->byte_size +
|
|
chnl_mgr_obj->word_size - 1) /
|
|
(u16) chnl_mgr_obj->word_size;
|
|
#else
|
|
sm->output_id = chnl_id;
|
|
sm->output_size = (chnl_packet_obj->byte_size +
|
|
chnl_mgr_obj->word_size - 1) / chnl_mgr_obj->word_size;
|
|
#endif
|
|
sm->output_full = 1;
|
|
/* Indicate to the DSP we have written the output */
|
|
sm_interrupt_dsp(pio_mgr->bridge_context, MBX_PCPY_CLASS);
|
|
/* Notify client with IO completion record (keep EOS) */
|
|
chnl_packet_obj->status &= CHNL_IOCSTATEOS;
|
|
notify_chnl_complete(pchnl, chnl_packet_obj);
|
|
/* Notify if stream is done. */
|
|
if (chnl_packet_obj->status & CHNL_IOCSTATEOS)
|
|
ntfy_notify(pchnl->ntfy_obj, DSP_STREAMDONE);
|
|
|
|
func_end:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* ======== output_msg ========
|
|
* Copies messages from the message queues to the shared memory.
|
|
*/
|
|
static void output_msg(struct io_mgr *pio_mgr, struct msg_mgr *hmsg_mgr)
|
|
{
|
|
u32 num_msgs = 0;
|
|
u32 i;
|
|
struct msg_dspmsg *msg_output;
|
|
struct msg_frame *pmsg;
|
|
struct msg_ctrl *msg_ctr_obj;
|
|
u32 val;
|
|
u32 addr;
|
|
|
|
msg_ctr_obj = pio_mgr->msg_output_ctrl;
|
|
|
|
/* Check if output has been cleared */
|
|
if (!msg_ctr_obj->buf_empty)
|
|
return;
|
|
|
|
num_msgs = (hmsg_mgr->msgs_pending > hmsg_mgr->max_msgs) ?
|
|
hmsg_mgr->max_msgs : hmsg_mgr->msgs_pending;
|
|
msg_output = (struct msg_dspmsg *) pio_mgr->msg_output;
|
|
|
|
/* Copy num_msgs messages into shared memory */
|
|
for (i = 0; i < num_msgs; i++) {
|
|
if (list_empty(&hmsg_mgr->msg_used_list))
|
|
continue;
|
|
|
|
pmsg = list_first_entry(&hmsg_mgr->msg_used_list,
|
|
struct msg_frame, list_elem);
|
|
list_del(&pmsg->list_elem);
|
|
|
|
val = (pmsg->msg_data).msgq_id;
|
|
addr = (u32) &msg_output->msgq_id;
|
|
write_ext32_bit_dsp_data(pio_mgr->bridge_context, addr, val);
|
|
|
|
val = (pmsg->msg_data).msg.cmd;
|
|
addr = (u32) &msg_output->msg.cmd;
|
|
write_ext32_bit_dsp_data(pio_mgr->bridge_context, addr, val);
|
|
|
|
val = (pmsg->msg_data).msg.arg1;
|
|
addr = (u32) &msg_output->msg.arg1;
|
|
write_ext32_bit_dsp_data(pio_mgr->bridge_context, addr, val);
|
|
|
|
val = (pmsg->msg_data).msg.arg2;
|
|
addr = (u32) &msg_output->msg.arg2;
|
|
write_ext32_bit_dsp_data(pio_mgr->bridge_context, addr, val);
|
|
|
|
msg_output++;
|
|
list_add_tail(&pmsg->list_elem, &hmsg_mgr->msg_free_list);
|
|
sync_set_event(hmsg_mgr->sync_event);
|
|
}
|
|
|
|
if (num_msgs > 0) {
|
|
hmsg_mgr->msgs_pending -= num_msgs;
|
|
#if _CHNL_WORDSIZE == 2
|
|
/*
|
|
* Access can be different SM access word size
|
|
* (e.g. 16/32 bit words)
|
|
*/
|
|
msg_ctr_obj->size = (u16) num_msgs;
|
|
#else
|
|
msg_ctr_obj->size = num_msgs;
|
|
#endif
|
|
msg_ctr_obj->buf_empty = false;
|
|
/* Set the post SWI flag */
|
|
msg_ctr_obj->post_swi = true;
|
|
/* Tell the DSP we have written the output. */
|
|
sm_interrupt_dsp(pio_mgr->bridge_context, MBX_PCPY_CLASS);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ======== register_shm_segs ========
|
|
* purpose:
|
|
* Registers GPP SM segment with CMM.
|
|
*/
|
|
static int register_shm_segs(struct io_mgr *hio_mgr,
|
|
struct cod_manager *cod_man,
|
|
u32 dw_gpp_base_pa)
|
|
{
|
|
int status = 0;
|
|
u32 ul_shm0_base = 0;
|
|
u32 shm0_end = 0;
|
|
u32 ul_shm0_rsrvd_start = 0;
|
|
u32 ul_rsrvd_size = 0;
|
|
u32 ul_gpp_phys;
|
|
u32 ul_dsp_virt;
|
|
u32 ul_shm_seg_id0 = 0;
|
|
u32 dw_offset, dw_gpp_base_va, ul_dsp_size;
|
|
|
|
/*
|
|
* Read address and size info for first SM region.
|
|
* Get start of 1st SM Heap region.
|
|
*/
|
|
status =
|
|
cod_get_sym_value(cod_man, SHM0_SHARED_BASE_SYM, &ul_shm0_base);
|
|
if (ul_shm0_base == 0) {
|
|
status = -EPERM;
|
|
goto func_end;
|
|
}
|
|
/* Get end of 1st SM Heap region */
|
|
if (!status) {
|
|
/* Get start and length of message part of shared memory */
|
|
status = cod_get_sym_value(cod_man, SHM0_SHARED_END_SYM,
|
|
&shm0_end);
|
|
if (shm0_end == 0) {
|
|
status = -EPERM;
|
|
goto func_end;
|
|
}
|
|
}
|
|
/* Start of Gpp reserved region */
|
|
if (!status) {
|
|
/* Get start and length of message part of shared memory */
|
|
status =
|
|
cod_get_sym_value(cod_man, SHM0_SHARED_RESERVED_BASE_SYM,
|
|
&ul_shm0_rsrvd_start);
|
|
if (ul_shm0_rsrvd_start == 0) {
|
|
status = -EPERM;
|
|
goto func_end;
|
|
}
|
|
}
|
|
/* Register with CMM */
|
|
if (!status) {
|
|
status = dev_get_cmm_mgr(hio_mgr->dev_obj, &hio_mgr->cmm_mgr);
|
|
if (!status) {
|
|
status = cmm_un_register_gppsm_seg(hio_mgr->cmm_mgr,
|
|
CMM_ALLSEGMENTS);
|
|
}
|
|
}
|
|
/* Register new SM region(s) */
|
|
if (!status && (shm0_end - ul_shm0_base) > 0) {
|
|
/* Calc size (bytes) of SM the GPP can alloc from */
|
|
ul_rsrvd_size =
|
|
(shm0_end - ul_shm0_rsrvd_start + 1) * hio_mgr->word_size;
|
|
if (ul_rsrvd_size <= 0) {
|
|
status = -EPERM;
|
|
goto func_end;
|
|
}
|
|
/* Calc size of SM DSP can alloc from */
|
|
ul_dsp_size =
|
|
(ul_shm0_rsrvd_start - ul_shm0_base) * hio_mgr->word_size;
|
|
if (ul_dsp_size <= 0) {
|
|
status = -EPERM;
|
|
goto func_end;
|
|
}
|
|
/* First TLB entry reserved for Bridge SM use. */
|
|
ul_gpp_phys = hio_mgr->ext_proc_info.ty_tlb[0].gpp_phys;
|
|
/* Get size in bytes */
|
|
ul_dsp_virt =
|
|
hio_mgr->ext_proc_info.ty_tlb[0].dsp_virt *
|
|
hio_mgr->word_size;
|
|
/*
|
|
* Calc byte offset used to convert GPP phys <-> DSP byte
|
|
* address.
|
|
*/
|
|
if (dw_gpp_base_pa > ul_dsp_virt)
|
|
dw_offset = dw_gpp_base_pa - ul_dsp_virt;
|
|
else
|
|
dw_offset = ul_dsp_virt - dw_gpp_base_pa;
|
|
|
|
if (ul_shm0_rsrvd_start * hio_mgr->word_size < ul_dsp_virt) {
|
|
status = -EPERM;
|
|
goto func_end;
|
|
}
|
|
/*
|
|
* Calc Gpp phys base of SM region.
|
|
* This is actually uncached kernel virtual address.
|
|
*/
|
|
dw_gpp_base_va =
|
|
ul_gpp_phys + ul_shm0_rsrvd_start * hio_mgr->word_size -
|
|
ul_dsp_virt;
|
|
/*
|
|
* Calc Gpp phys base of SM region.
|
|
* This is the physical address.
|
|
*/
|
|
dw_gpp_base_pa =
|
|
dw_gpp_base_pa + ul_shm0_rsrvd_start * hio_mgr->word_size -
|
|
ul_dsp_virt;
|
|
/* Register SM Segment 0. */
|
|
status =
|
|
cmm_register_gppsm_seg(hio_mgr->cmm_mgr, dw_gpp_base_pa,
|
|
ul_rsrvd_size, dw_offset,
|
|
(dw_gpp_base_pa >
|
|
ul_dsp_virt) ? CMM_ADDTODSPPA :
|
|
CMM_SUBFROMDSPPA,
|
|
(u32) (ul_shm0_base *
|
|
hio_mgr->word_size),
|
|
ul_dsp_size, &ul_shm_seg_id0,
|
|
dw_gpp_base_va);
|
|
/* First SM region is seg_id = 1 */
|
|
if (ul_shm_seg_id0 != 1)
|
|
status = -EPERM;
|
|
}
|
|
func_end:
|
|
return status;
|
|
}
|
|
|
|
/* ZCPY IO routines. */
|
|
/*
|
|
* ======== IO_SHMcontrol ========
|
|
* Sets the requested shm setting.
|
|
*/
|
|
int io_sh_msetting(struct io_mgr *hio_mgr, u8 desc, void *pargs)
|
|
{
|
|
#ifdef CONFIG_TIDSPBRIDGE_DVFS
|
|
u32 i;
|
|
struct dspbridge_platform_data *pdata =
|
|
omap_dspbridge_dev->dev.platform_data;
|
|
|
|
switch (desc) {
|
|
case SHM_CURROPP:
|
|
/* Update the shared memory with requested OPP information */
|
|
if (pargs != NULL)
|
|
hio_mgr->shared_mem->opp_table_struct.curr_opp_pt =
|
|
*(u32 *) pargs;
|
|
else
|
|
return -EPERM;
|
|
break;
|
|
case SHM_OPPINFO:
|
|
/*
|
|
* Update the shared memory with the voltage, frequency,
|
|
* min and max frequency values for an OPP.
|
|
*/
|
|
for (i = 0; i <= dsp_max_opps; i++) {
|
|
hio_mgr->shared_mem->opp_table_struct.opp_point[i].
|
|
voltage = vdd1_dsp_freq[i][0];
|
|
dev_dbg(bridge, "OPP-shm: voltage: %d\n",
|
|
vdd1_dsp_freq[i][0]);
|
|
hio_mgr->shared_mem->opp_table_struct.
|
|
opp_point[i].frequency = vdd1_dsp_freq[i][1];
|
|
dev_dbg(bridge, "OPP-shm: frequency: %d\n",
|
|
vdd1_dsp_freq[i][1]);
|
|
hio_mgr->shared_mem->opp_table_struct.opp_point[i].
|
|
min_freq = vdd1_dsp_freq[i][2];
|
|
dev_dbg(bridge, "OPP-shm: min freq: %d\n",
|
|
vdd1_dsp_freq[i][2]);
|
|
hio_mgr->shared_mem->opp_table_struct.opp_point[i].
|
|
max_freq = vdd1_dsp_freq[i][3];
|
|
dev_dbg(bridge, "OPP-shm: max freq: %d\n",
|
|
vdd1_dsp_freq[i][3]);
|
|
}
|
|
hio_mgr->shared_mem->opp_table_struct.num_opp_pts =
|
|
dsp_max_opps;
|
|
dev_dbg(bridge, "OPP-shm: max OPP number: %d\n", dsp_max_opps);
|
|
/* Update the current OPP number */
|
|
if (pdata->dsp_get_opp)
|
|
i = (*pdata->dsp_get_opp) ();
|
|
hio_mgr->shared_mem->opp_table_struct.curr_opp_pt = i;
|
|
dev_dbg(bridge, "OPP-shm: value programmed = %d\n", i);
|
|
break;
|
|
case SHM_GETOPP:
|
|
/* Get the OPP that DSP has requested */
|
|
*(u32 *) pargs = hio_mgr->shared_mem->opp_request.rqst_opp_pt;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_io_get_proc_load ========
|
|
* Gets the Processor's Load information
|
|
*/
|
|
int bridge_io_get_proc_load(struct io_mgr *hio_mgr,
|
|
struct dsp_procloadstat *proc_lstat)
|
|
{
|
|
if (!hio_mgr->shared_mem)
|
|
return -EFAULT;
|
|
|
|
proc_lstat->curr_load =
|
|
hio_mgr->shared_mem->load_mon_info.curr_dsp_load;
|
|
proc_lstat->predicted_load =
|
|
hio_mgr->shared_mem->load_mon_info.pred_dsp_load;
|
|
proc_lstat->curr_dsp_freq =
|
|
hio_mgr->shared_mem->load_mon_info.curr_dsp_freq;
|
|
proc_lstat->predicted_freq =
|
|
hio_mgr->shared_mem->load_mon_info.pred_dsp_freq;
|
|
|
|
dev_dbg(bridge, "Curr Load = %d, Pred Load = %d, Curr Freq = %d, "
|
|
"Pred Freq = %d\n", proc_lstat->curr_load,
|
|
proc_lstat->predicted_load, proc_lstat->curr_dsp_freq,
|
|
proc_lstat->predicted_freq);
|
|
return 0;
|
|
}
|
|
|
|
|
|
#if defined(CONFIG_TIDSPBRIDGE_BACKTRACE)
|
|
void print_dsp_debug_trace(struct io_mgr *hio_mgr)
|
|
{
|
|
u32 ul_new_message_length = 0, ul_gpp_cur_pointer;
|
|
|
|
while (true) {
|
|
/* Get the DSP current pointer */
|
|
ul_gpp_cur_pointer =
|
|
*(u32 *) (hio_mgr->trace_buffer_current);
|
|
ul_gpp_cur_pointer =
|
|
hio_mgr->gpp_va + (ul_gpp_cur_pointer -
|
|
hio_mgr->dsp_va);
|
|
|
|
/* No new debug messages available yet */
|
|
if (ul_gpp_cur_pointer == hio_mgr->gpp_read_pointer) {
|
|
break;
|
|
} else if (ul_gpp_cur_pointer > hio_mgr->gpp_read_pointer) {
|
|
/* Continuous data */
|
|
ul_new_message_length =
|
|
ul_gpp_cur_pointer - hio_mgr->gpp_read_pointer;
|
|
|
|
memcpy(hio_mgr->msg,
|
|
(char *)hio_mgr->gpp_read_pointer,
|
|
ul_new_message_length);
|
|
hio_mgr->msg[ul_new_message_length] = '\0';
|
|
/*
|
|
* Advance the GPP trace pointer to DSP current
|
|
* pointer.
|
|
*/
|
|
hio_mgr->gpp_read_pointer += ul_new_message_length;
|
|
/* Print the trace messages */
|
|
pr_info("DSPTrace: %s\n", hio_mgr->msg);
|
|
} else if (ul_gpp_cur_pointer < hio_mgr->gpp_read_pointer) {
|
|
/* Handle trace buffer wraparound */
|
|
memcpy(hio_mgr->msg,
|
|
(char *)hio_mgr->gpp_read_pointer,
|
|
hio_mgr->trace_buffer_end -
|
|
hio_mgr->gpp_read_pointer);
|
|
ul_new_message_length =
|
|
ul_gpp_cur_pointer - hio_mgr->trace_buffer_begin;
|
|
memcpy(&hio_mgr->msg[hio_mgr->trace_buffer_end -
|
|
hio_mgr->gpp_read_pointer],
|
|
(char *)hio_mgr->trace_buffer_begin,
|
|
ul_new_message_length);
|
|
hio_mgr->msg[hio_mgr->trace_buffer_end -
|
|
hio_mgr->gpp_read_pointer +
|
|
ul_new_message_length] = '\0';
|
|
/*
|
|
* Advance the GPP trace pointer to DSP current
|
|
* pointer.
|
|
*/
|
|
hio_mgr->gpp_read_pointer =
|
|
hio_mgr->trace_buffer_begin +
|
|
ul_new_message_length;
|
|
/* Print the trace messages */
|
|
pr_info("DSPTrace: %s\n", hio_mgr->msg);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
|
|
/*
|
|
* ======== print_dsp_trace_buffer ========
|
|
* Prints the trace buffer returned from the DSP (if DBG_Trace is enabled).
|
|
* Parameters:
|
|
* hdeh_mgr: Handle to DEH manager object
|
|
* number of extra carriage returns to generate.
|
|
* Returns:
|
|
* 0: Success.
|
|
* -ENOMEM: Unable to allocate memory.
|
|
* Requires:
|
|
* hdeh_mgr muse be valid. Checked in bridge_deh_notify.
|
|
*/
|
|
int print_dsp_trace_buffer(struct bridge_dev_context *hbridge_context)
|
|
{
|
|
int status = 0;
|
|
struct cod_manager *cod_mgr;
|
|
u32 ul_trace_end;
|
|
u32 ul_trace_begin;
|
|
u32 trace_cur_pos;
|
|
u32 ul_num_bytes = 0;
|
|
u32 ul_num_words = 0;
|
|
u32 ul_word_size = 2;
|
|
char *psz_buf;
|
|
char *str_beg;
|
|
char *trace_end;
|
|
char *buf_end;
|
|
char *new_line;
|
|
|
|
struct bridge_dev_context *pbridge_context = hbridge_context;
|
|
struct bridge_drv_interface *intf_fxns;
|
|
struct dev_object *dev_obj = (struct dev_object *)
|
|
pbridge_context->dev_obj;
|
|
|
|
status = dev_get_cod_mgr(dev_obj, &cod_mgr);
|
|
|
|
if (cod_mgr) {
|
|
/* Look for SYS_PUTCBEG/SYS_PUTCEND */
|
|
status =
|
|
cod_get_sym_value(cod_mgr, COD_TRACEBEG, &ul_trace_begin);
|
|
} else {
|
|
status = -EFAULT;
|
|
}
|
|
if (!status)
|
|
status =
|
|
cod_get_sym_value(cod_mgr, COD_TRACEEND, &ul_trace_end);
|
|
|
|
if (!status)
|
|
/* trace_cur_pos will hold the address of a DSP pointer */
|
|
status = cod_get_sym_value(cod_mgr, COD_TRACECURPOS,
|
|
&trace_cur_pos);
|
|
|
|
if (status)
|
|
goto func_end;
|
|
|
|
ul_num_bytes = (ul_trace_end - ul_trace_begin);
|
|
|
|
ul_num_words = ul_num_bytes * ul_word_size;
|
|
status = dev_get_intf_fxns(dev_obj, &intf_fxns);
|
|
|
|
if (status)
|
|
goto func_end;
|
|
|
|
psz_buf = kzalloc(ul_num_bytes + 2, GFP_ATOMIC);
|
|
if (psz_buf != NULL) {
|
|
/* Read trace buffer data */
|
|
status = (*intf_fxns->brd_read)(pbridge_context,
|
|
(u8 *)psz_buf, (u32)ul_trace_begin,
|
|
ul_num_bytes, 0);
|
|
|
|
if (status)
|
|
goto func_end;
|
|
|
|
/* Pack and do newline conversion */
|
|
pr_debug("PrintDspTraceBuffer: "
|
|
"before pack and unpack.\n");
|
|
pr_debug("%s: DSP Trace Buffer Begin:\n"
|
|
"=======================\n%s\n",
|
|
__func__, psz_buf);
|
|
|
|
/* Read the value at the DSP address in trace_cur_pos. */
|
|
status = (*intf_fxns->brd_read)(pbridge_context,
|
|
(u8 *)&trace_cur_pos, (u32)trace_cur_pos,
|
|
4, 0);
|
|
if (status)
|
|
goto func_end;
|
|
/* Pack and do newline conversion */
|
|
pr_info("DSP Trace Buffer Begin:\n"
|
|
"=======================\n%s\n",
|
|
psz_buf);
|
|
|
|
|
|
/* convert to offset */
|
|
trace_cur_pos = trace_cur_pos - ul_trace_begin;
|
|
|
|
if (ul_num_bytes) {
|
|
/*
|
|
* The buffer is not full, find the end of the
|
|
* data -- buf_end will be >= pszBuf after
|
|
* while.
|
|
*/
|
|
buf_end = &psz_buf[ul_num_bytes+1];
|
|
/* DSP print position */
|
|
trace_end = &psz_buf[trace_cur_pos];
|
|
|
|
/*
|
|
* Search buffer for a new_line and replace it
|
|
* with '\0', then print as string.
|
|
* Continue until end of buffer is reached.
|
|
*/
|
|
str_beg = trace_end;
|
|
ul_num_bytes = buf_end - str_beg;
|
|
|
|
while (str_beg < buf_end) {
|
|
new_line = strnchr(str_beg, ul_num_bytes,
|
|
'\n');
|
|
if (new_line && new_line < buf_end) {
|
|
*new_line = 0;
|
|
pr_debug("%s\n", str_beg);
|
|
str_beg = ++new_line;
|
|
ul_num_bytes = buf_end - str_beg;
|
|
} else {
|
|
/*
|
|
* Assume buffer empty if it contains
|
|
* a zero
|
|
*/
|
|
if (*str_beg != '\0') {
|
|
str_beg[ul_num_bytes] = 0;
|
|
pr_debug("%s\n", str_beg);
|
|
}
|
|
str_beg = buf_end;
|
|
ul_num_bytes = 0;
|
|
}
|
|
}
|
|
/*
|
|
* Search buffer for a nNewLine and replace it
|
|
* with '\0', then print as string.
|
|
* Continue until buffer is exhausted.
|
|
*/
|
|
str_beg = psz_buf;
|
|
ul_num_bytes = trace_end - str_beg;
|
|
|
|
while (str_beg < trace_end) {
|
|
new_line = strnchr(str_beg, ul_num_bytes, '\n');
|
|
if (new_line != NULL && new_line < trace_end) {
|
|
*new_line = 0;
|
|
pr_debug("%s\n", str_beg);
|
|
str_beg = ++new_line;
|
|
ul_num_bytes = trace_end - str_beg;
|
|
} else {
|
|
/*
|
|
* Assume buffer empty if it contains
|
|
* a zero
|
|
*/
|
|
if (*str_beg != '\0') {
|
|
str_beg[ul_num_bytes] = 0;
|
|
pr_debug("%s\n", str_beg);
|
|
}
|
|
str_beg = trace_end;
|
|
ul_num_bytes = 0;
|
|
}
|
|
}
|
|
}
|
|
pr_info("\n=======================\n"
|
|
"DSP Trace Buffer End:\n");
|
|
kfree(psz_buf);
|
|
} else {
|
|
status = -ENOMEM;
|
|
}
|
|
func_end:
|
|
if (status)
|
|
dev_dbg(bridge, "%s Failed, status 0x%x\n", __func__, status);
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* dump_dsp_stack() - This function dumps the data on the DSP stack.
|
|
* @bridge_context: Bridge driver's device context pointer.
|
|
*
|
|
*/
|
|
int dump_dsp_stack(struct bridge_dev_context *bridge_context)
|
|
{
|
|
int status = 0;
|
|
struct cod_manager *code_mgr;
|
|
struct node_mgr *node_mgr;
|
|
u32 trace_begin;
|
|
char name[256];
|
|
struct {
|
|
u32 head[2];
|
|
u32 size;
|
|
} mmu_fault_dbg_info;
|
|
u32 *buffer;
|
|
u32 *buffer_beg;
|
|
u32 *buffer_end;
|
|
u32 exc_type;
|
|
u32 dyn_ext_base;
|
|
u32 i;
|
|
u32 offset_output;
|
|
u32 total_size;
|
|
u32 poll_cnt;
|
|
const char *dsp_regs[] = {"EFR", "IERR", "ITSR", "NTSR",
|
|
"IRP", "NRP", "AMR", "SSR",
|
|
"ILC", "RILC", "IER", "CSR"};
|
|
const char *exec_ctxt[] = {"Task", "SWI", "HWI", "Unknown"};
|
|
struct bridge_drv_interface *intf_fxns;
|
|
struct dev_object *dev_object = bridge_context->dev_obj;
|
|
|
|
status = dev_get_cod_mgr(dev_object, &code_mgr);
|
|
if (!code_mgr) {
|
|
pr_debug("%s: Failed on dev_get_cod_mgr.\n", __func__);
|
|
status = -EFAULT;
|
|
}
|
|
|
|
if (!status) {
|
|
status = dev_get_node_manager(dev_object, &node_mgr);
|
|
if (!node_mgr) {
|
|
pr_debug("%s: Failed on dev_get_node_manager.\n",
|
|
__func__);
|
|
status = -EFAULT;
|
|
}
|
|
}
|
|
|
|
if (!status) {
|
|
/* Look for SYS_PUTCBEG/SYS_PUTCEND: */
|
|
status =
|
|
cod_get_sym_value(code_mgr, COD_TRACEBEG, &trace_begin);
|
|
pr_debug("%s: trace_begin Value 0x%x\n",
|
|
__func__, trace_begin);
|
|
if (status)
|
|
pr_debug("%s: Failed on cod_get_sym_value.\n",
|
|
__func__);
|
|
}
|
|
if (!status)
|
|
status = dev_get_intf_fxns(dev_object, &intf_fxns);
|
|
/*
|
|
* Check for the "magic number" in the trace buffer. If it has
|
|
* yet to appear then poll the trace buffer to wait for it. Its
|
|
* appearance signals that the DSP has finished dumping its state.
|
|
*/
|
|
mmu_fault_dbg_info.head[0] = 0;
|
|
mmu_fault_dbg_info.head[1] = 0;
|
|
if (!status) {
|
|
poll_cnt = 0;
|
|
while ((mmu_fault_dbg_info.head[0] != MMU_FAULT_HEAD1 ||
|
|
mmu_fault_dbg_info.head[1] != MMU_FAULT_HEAD2) &&
|
|
poll_cnt < POLL_MAX) {
|
|
|
|
/* Read DSP dump size from the DSP trace buffer... */
|
|
status = (*intf_fxns->brd_read)(bridge_context,
|
|
(u8 *)&mmu_fault_dbg_info, (u32)trace_begin,
|
|
sizeof(mmu_fault_dbg_info), 0);
|
|
|
|
if (status)
|
|
break;
|
|
|
|
poll_cnt++;
|
|
}
|
|
|
|
if (mmu_fault_dbg_info.head[0] != MMU_FAULT_HEAD1 &&
|
|
mmu_fault_dbg_info.head[1] != MMU_FAULT_HEAD2) {
|
|
status = -ETIME;
|
|
pr_err("%s:No DSP MMU-Fault information available.\n",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
if (!status) {
|
|
total_size = mmu_fault_dbg_info.size;
|
|
/* Limit the size in case DSP went crazy */
|
|
if (total_size > MAX_MMU_DBGBUFF)
|
|
total_size = MAX_MMU_DBGBUFF;
|
|
|
|
buffer = kzalloc(total_size, GFP_ATOMIC);
|
|
if (!buffer) {
|
|
status = -ENOMEM;
|
|
pr_debug("%s: Failed to "
|
|
"allocate stack dump buffer.\n", __func__);
|
|
goto func_end;
|
|
}
|
|
|
|
buffer_beg = buffer;
|
|
buffer_end = buffer + total_size / 4;
|
|
|
|
/* Read bytes from the DSP trace buffer... */
|
|
status = (*intf_fxns->brd_read)(bridge_context,
|
|
(u8 *)buffer, (u32)trace_begin,
|
|
total_size, 0);
|
|
if (status) {
|
|
pr_debug("%s: Failed to Read Trace Buffer.\n",
|
|
__func__);
|
|
goto func_end;
|
|
}
|
|
|
|
pr_err("\nAproximate Crash Position:\n"
|
|
"--------------------------\n");
|
|
|
|
exc_type = buffer[3];
|
|
if (!exc_type)
|
|
i = buffer[79]; /* IRP */
|
|
else
|
|
i = buffer[80]; /* NRP */
|
|
|
|
status =
|
|
cod_get_sym_value(code_mgr, DYNEXTBASE, &dyn_ext_base);
|
|
if (status) {
|
|
status = -EFAULT;
|
|
goto func_end;
|
|
}
|
|
|
|
if ((i > dyn_ext_base) && (node_find_addr(node_mgr, i,
|
|
0x1000, &offset_output, name) == 0))
|
|
pr_err("0x%-8x [\"%s\" + 0x%x]\n", i, name,
|
|
i - offset_output);
|
|
else
|
|
pr_err("0x%-8x [Unable to match to a symbol.]\n", i);
|
|
|
|
buffer += 4;
|
|
|
|
pr_err("\nExecution Info:\n"
|
|
"---------------\n");
|
|
|
|
if (*buffer < ARRAY_SIZE(exec_ctxt)) {
|
|
pr_err("Execution context \t%s\n",
|
|
exec_ctxt[*buffer++]);
|
|
} else {
|
|
pr_err("Execution context corrupt\n");
|
|
kfree(buffer_beg);
|
|
return -EFAULT;
|
|
}
|
|
pr_err("Task Handle\t\t0x%x\n", *buffer++);
|
|
pr_err("Stack Pointer\t\t0x%x\n", *buffer++);
|
|
pr_err("Stack Top\t\t0x%x\n", *buffer++);
|
|
pr_err("Stack Bottom\t\t0x%x\n", *buffer++);
|
|
pr_err("Stack Size\t\t0x%x\n", *buffer++);
|
|
pr_err("Stack Size In Use\t0x%x\n", *buffer++);
|
|
|
|
pr_err("\nCPU Registers\n"
|
|
"---------------\n");
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
if (i == 4 || i == 6 || i == 8)
|
|
pr_err("A%d 0x%-8x [Function Argument %d]\n",
|
|
i, *buffer++, i-3);
|
|
else if (i == 15)
|
|
pr_err("A15 0x%-8x [Frame Pointer]\n",
|
|
*buffer++);
|
|
else
|
|
pr_err("A%d 0x%x\n", i, *buffer++);
|
|
}
|
|
|
|
pr_err("\nB0 0x%x\n", *buffer++);
|
|
pr_err("B1 0x%x\n", *buffer++);
|
|
pr_err("B2 0x%x\n", *buffer++);
|
|
|
|
if ((*buffer > dyn_ext_base) && (node_find_addr(node_mgr,
|
|
*buffer, 0x1000, &offset_output, name) == 0))
|
|
|
|
pr_err("B3 0x%-8x [Function Return Pointer:"
|
|
" \"%s\" + 0x%x]\n", *buffer, name,
|
|
*buffer - offset_output);
|
|
else
|
|
pr_err("B3 0x%-8x [Function Return Pointer:"
|
|
"Unable to match to a symbol.]\n", *buffer);
|
|
|
|
buffer++;
|
|
|
|
for (i = 4; i < 32; i++) {
|
|
if (i == 4 || i == 6 || i == 8)
|
|
pr_err("B%d 0x%-8x [Function Argument %d]\n",
|
|
i, *buffer++, i-2);
|
|
else if (i == 14)
|
|
pr_err("B14 0x%-8x [Data Page Pointer]\n",
|
|
*buffer++);
|
|
else
|
|
pr_err("B%d 0x%x\n", i, *buffer++);
|
|
}
|
|
|
|
pr_err("\n");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dsp_regs); i++)
|
|
pr_err("%s 0x%x\n", dsp_regs[i], *buffer++);
|
|
|
|
pr_err("\nStack:\n"
|
|
"------\n");
|
|
|
|
for (i = 0; buffer < buffer_end; i++, buffer++) {
|
|
if ((*buffer > dyn_ext_base) && (
|
|
node_find_addr(node_mgr, *buffer , 0x600,
|
|
&offset_output, name) == 0))
|
|
pr_err("[%d] 0x%-8x [\"%s\" + 0x%x]\n",
|
|
i, *buffer, name,
|
|
*buffer - offset_output);
|
|
else
|
|
pr_err("[%d] 0x%x\n", i, *buffer);
|
|
}
|
|
kfree(buffer_beg);
|
|
}
|
|
func_end:
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* dump_dl_modules() - This functions dumps the _DLModules loaded in DSP side
|
|
* @bridge_context: Bridge driver's device context pointer.
|
|
*
|
|
*/
|
|
void dump_dl_modules(struct bridge_dev_context *bridge_context)
|
|
{
|
|
struct cod_manager *code_mgr;
|
|
struct bridge_drv_interface *intf_fxns;
|
|
struct bridge_dev_context *bridge_ctxt = bridge_context;
|
|
struct dev_object *dev_object = bridge_ctxt->dev_obj;
|
|
struct modules_header modules_hdr;
|
|
struct dll_module *module_struct = NULL;
|
|
u32 module_dsp_addr;
|
|
u32 module_size;
|
|
u32 module_struct_size = 0;
|
|
u32 sect_ndx;
|
|
char *sect_str ;
|
|
int status = 0;
|
|
|
|
status = dev_get_intf_fxns(dev_object, &intf_fxns);
|
|
if (status) {
|
|
pr_debug("%s: Failed on dev_get_intf_fxns.\n", __func__);
|
|
goto func_end;
|
|
}
|
|
|
|
status = dev_get_cod_mgr(dev_object, &code_mgr);
|
|
if (!code_mgr) {
|
|
pr_debug("%s: Failed on dev_get_cod_mgr.\n", __func__);
|
|
status = -EFAULT;
|
|
goto func_end;
|
|
}
|
|
|
|
/* Lookup the address of the modules_header structure */
|
|
status = cod_get_sym_value(code_mgr, "_DLModules", &module_dsp_addr);
|
|
if (status) {
|
|
pr_debug("%s: Failed on cod_get_sym_value for _DLModules.\n",
|
|
__func__);
|
|
goto func_end;
|
|
}
|
|
|
|
pr_debug("%s: _DLModules at 0x%x\n", __func__, module_dsp_addr);
|
|
|
|
/* Copy the modules_header structure from DSP memory. */
|
|
status = (*intf_fxns->brd_read)(bridge_context, (u8 *) &modules_hdr,
|
|
(u32) module_dsp_addr, sizeof(modules_hdr), 0);
|
|
|
|
if (status) {
|
|
pr_debug("%s: Failed failed to read modules header.\n",
|
|
__func__);
|
|
goto func_end;
|
|
}
|
|
|
|
module_dsp_addr = modules_hdr.first_module;
|
|
module_size = modules_hdr.first_module_size;
|
|
|
|
pr_debug("%s: dll_module_header 0x%x %d\n", __func__, module_dsp_addr,
|
|
module_size);
|
|
|
|
pr_err("\nDynamically Loaded Modules:\n"
|
|
"---------------------------\n");
|
|
|
|
/* For each dll_module structure in the list... */
|
|
while (module_size) {
|
|
/*
|
|
* Allocate/re-allocate memory to hold the dll_module
|
|
* structure. The memory is re-allocated only if the existing
|
|
* allocation is too small.
|
|
*/
|
|
if (module_size > module_struct_size) {
|
|
kfree(module_struct);
|
|
module_struct = kzalloc(module_size+128, GFP_ATOMIC);
|
|
module_struct_size = module_size+128;
|
|
pr_debug("%s: allocated module struct %p %d\n",
|
|
__func__, module_struct, module_struct_size);
|
|
if (!module_struct)
|
|
goto func_end;
|
|
}
|
|
/* Copy the dll_module structure from DSP memory */
|
|
status = (*intf_fxns->brd_read)(bridge_context,
|
|
(u8 *)module_struct, module_dsp_addr, module_size, 0);
|
|
|
|
if (status) {
|
|
pr_debug(
|
|
"%s: Failed to read dll_module struct for 0x%x.\n",
|
|
__func__, module_dsp_addr);
|
|
break;
|
|
}
|
|
|
|
/* Update info regarding the _next_ module in the list. */
|
|
module_dsp_addr = module_struct->next_module;
|
|
module_size = module_struct->next_module_size;
|
|
|
|
pr_debug("%s: next module 0x%x %d, this module num sects %d\n",
|
|
__func__, module_dsp_addr, module_size,
|
|
module_struct->num_sects);
|
|
|
|
/*
|
|
* The section name strings start immediately following
|
|
* the array of dll_sect structures.
|
|
*/
|
|
sect_str = (char *) &module_struct->
|
|
sects[module_struct->num_sects];
|
|
pr_err("%s\n", sect_str);
|
|
|
|
/*
|
|
* Advance to the first section name string.
|
|
* Each string follows the one before.
|
|
*/
|
|
sect_str += strlen(sect_str) + 1;
|
|
|
|
/* Access each dll_sect structure and its name string. */
|
|
for (sect_ndx = 0;
|
|
sect_ndx < module_struct->num_sects; sect_ndx++) {
|
|
pr_err(" Section: 0x%x ",
|
|
module_struct->sects[sect_ndx].sect_load_adr);
|
|
|
|
if (((u32) sect_str - (u32) module_struct) <
|
|
module_struct_size) {
|
|
pr_err("%s\n", sect_str);
|
|
/* Each string follows the one before. */
|
|
sect_str += strlen(sect_str)+1;
|
|
} else {
|
|
pr_err("<string error>\n");
|
|
pr_debug("%s: section name sting address "
|
|
"is invalid %p\n", __func__, sect_str);
|
|
}
|
|
}
|
|
}
|
|
func_end:
|
|
kfree(module_struct);
|
|
}
|
|
#endif
|