| /* |
| * 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. |
| */ |
| |
| /* Host OS */ |
| #include <dspbridge/host_os.h> |
| #include <linux/workqueue.h> |
| |
| /* ----------------------------------- DSP/BIOS Bridge */ |
| #include <dspbridge/std.h> |
| #include <dspbridge/dbdefs.h> |
| |
| /* Trace & Debug */ |
| #include <dspbridge/dbc.h> |
| |
| /* Services Layer */ |
| #include <dspbridge/cfg.h> |
| #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 *hbridge_context; |
| /* Function interface to Bridge driver */ |
| struct bridge_drv_interface *intf_fxns; |
| struct dev_object *hdev_obj; /* Device this board represents */ |
| |
| /* These fields initialized in bridge_io_create() */ |
| struct chnl_mgr *hchnl_mgr; |
| struct shm *shared_mem; /* Shared Memory control */ |
| u8 *input; /* Address of input channel */ |
| u8 *output; /* Address of output channel */ |
| struct msg_mgr *hmsg_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 usm_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 *hcmm_mgr; /* Shared Mem Mngr */ |
| struct work_struct io_workq; /* workqueue */ |
| #if defined(CONFIG_TIDSPBRIDGE_BACKTRACE) || defined(CONFIG_TIDSPBRIDGE_DEBUG) |
| u32 ul_trace_buffer_begin; /* Trace message start address */ |
| u32 ul_trace_buffer_end; /* Trace message end address */ |
| u32 ul_trace_buffer_current; /* Trace message current address */ |
| u32 ul_gpp_read_pointer; /* GPP Read pointer to Trace buffer */ |
| u8 *pmsg; |
| u32 ul_gpp_va; |
| u32 ul_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; |
| |
| }; |
| |
| /* Function Prototypes */ |
| static void io_dispatch_chnl(IN struct io_mgr *pio_mgr, |
| IN OUT struct chnl_object *pchnl, u8 io_mode); |
| static void io_dispatch_msg(IN struct io_mgr *pio_mgr, |
| struct msg_mgr *hmsg_mgr); |
| 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); |
| static u32 read_data(struct bridge_dev_context *dev_ctxt, void *dest, |
| void *src, u32 usize); |
| static u32 write_data(struct bridge_dev_context *dev_ctxt, void *dest, |
| void *src, u32 usize); |
| |
| /* Bus Addr (cached kernel) */ |
| static int register_shm_segs(struct io_mgr *hio_mgr, |
| struct cod_manager *cod_man, |
| u32 dw_gpp_base_pa); |
| |
| /* |
| * ======== bridge_io_create ======== |
| * Create an IO manager object. |
| */ |
| int bridge_io_create(OUT struct io_mgr **io_man, |
| struct dev_object *hdev_obj, |
| IN CONST struct io_attrs *mgr_attrts) |
| { |
| int status = 0; |
| struct io_mgr *pio_mgr = NULL; |
| struct shm *shared_mem = 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) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| dev_get_chnl_mgr(hdev_obj, &hchnl_mgr); |
| if (!hchnl_mgr || hchnl_mgr->hio_mgr) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| /* |
| * 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) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| dev_get_dev_type(hdev_obj, &dev_type); |
| /* |
| * DSP shared memory area will get set properly when |
| * a program is loaded. They are unknown until a COFF file is |
| * loaded. I chose the value -1 because it was less likely to be |
| * a valid address than 0. |
| */ |
| shared_mem = (struct shm *)-1; |
| |
| /* Allocate IO manager object */ |
| pio_mgr = kzalloc(sizeof(struct io_mgr), GFP_KERNEL); |
| if (pio_mgr == NULL) { |
| status = -ENOMEM; |
| goto func_end; |
| } |
| |
| /* Initialize chnl_mgr object */ |
| #if defined(CONFIG_TIDSPBRIDGE_BACKTRACE) || defined(CONFIG_TIDSPBRIDGE_DEBUG) |
| pio_mgr->pmsg = NULL; |
| #endif |
| pio_mgr->hchnl_mgr = hchnl_mgr; |
| pio_mgr->word_size = mgr_attrts->word_size; |
| pio_mgr->shared_mem = shared_mem; |
| |
| 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 (DSP_SUCCEEDED(status)) |
| status = dev_get_dev_node(hdev_obj, &dev_node_obj); |
| } |
| |
| if (DSP_SUCCEEDED(status)) { |
| pio_mgr->hbridge_context = hbridge_context; |
| pio_mgr->shared_irq = mgr_attrts->irq_shared; |
| if (dsp_wdt_init()) |
| status = -EPERM; |
| } else { |
| status = -EIO; |
| } |
| func_end: |
| if (DSP_FAILED(status)) { |
| /* Cleanup */ |
| bridge_io_destroy(pio_mgr); |
| if (io_man) |
| *io_man = NULL; |
| } else { |
| /* Return IO manager object to caller... */ |
| hchnl_mgr->hio_mgr = pio_mgr; |
| *io_man = pio_mgr; |
| } |
| return status; |
| } |
| |
| /* |
| * ======== 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) || defined(CONFIG_TIDSPBRIDGE_DEBUG) |
| kfree(hio_mgr->pmsg); |
| #endif |
| dsp_wdt_exit(); |
| /* Free this IO manager object */ |
| kfree(hio_mgr); |
| } else { |
| status = -EFAULT; |
| } |
| |
| return 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 cod_manager *cod_man; |
| struct chnl_mgr *hchnl_mgr; |
| struct msg_mgr *hmsg_mgr; |
| u32 ul_shm_base; |
| u32 ul_shm_base_offset; |
| u32 ul_shm_limit; |
| u32 ul_shm_length = -1; |
| u32 ul_mem_length = -1; |
| u32 ul_msg_base; |
| u32 ul_msg_limit; |
| u32 ul_msg_length = -1; |
| u32 ul_ext_end; |
| u32 ul_gpp_pa = 0; |
| u32 ul_gpp_va = 0; |
| u32 ul_dsp_va = 0; |
| u32 ul_seg_size = 0; |
| u32 ul_pad_size = 0; |
| u32 i; |
| int status = 0; |
| u8 num_procs = 0; |
| s32 ndx = 0; |
| /* DSP MMU setup table */ |
| struct bridge_ioctl_extproc ae_proc[BRDIOCTL_NUMOFMMUTLB]; |
| struct cfg_hostres *host_res; |
| struct bridge_dev_context *pbridge_context; |
| u32 map_attrs; |
| u32 shm0_end; |
| u32 ul_dyn_ext_base; |
| u32 ul_seg1_size = 0; |
| u32 pa_curr = 0; |
| u32 va_curr = 0; |
| u32 gpp_va_curr = 0; |
| u32 num_bytes = 0; |
| u32 all_bits = 0; |
| u32 page_size[] = { HW_PAGE_SIZE16MB, HW_PAGE_SIZE1MB, |
| HW_PAGE_SIZE64KB, HW_PAGE_SIZE4KB |
| }; |
| |
| status = dev_get_bridge_context(hio_mgr->hdev_obj, &pbridge_context); |
| if (!pbridge_context) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| |
| host_res = pbridge_context->resources; |
| if (!host_res) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| status = dev_get_cod_mgr(hio_mgr->hdev_obj, &cod_man); |
| if (!cod_man) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| hchnl_mgr = hio_mgr->hchnl_mgr; |
| /* The message manager is destroyed when the board is stopped. */ |
| dev_get_msg_mgr(hio_mgr->hdev_obj, &hio_mgr->hmsg_mgr); |
| hmsg_mgr = hio_mgr->hmsg_mgr; |
| if (!hchnl_mgr || !hmsg_mgr) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| if (hio_mgr->shared_mem) |
| hio_mgr->shared_mem = NULL; |
| |
| /* Get start and length of channel part of shared memory */ |
| status = cod_get_sym_value(cod_man, CHNL_SHARED_BUFFER_BASE_SYM, |
| &ul_shm_base); |
| if (DSP_FAILED(status)) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| status = cod_get_sym_value(cod_man, CHNL_SHARED_BUFFER_LIMIT_SYM, |
| &ul_shm_limit); |
| if (DSP_FAILED(status)) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| if (ul_shm_limit <= ul_shm_base) { |
| status = -EINVAL; |
| goto func_end; |
| } |
| /* Get total length in bytes */ |
| ul_shm_length = (ul_shm_limit - ul_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__, (ul_shm_length - sizeof(struct shm))); |
| |
| if (DSP_SUCCEEDED(status)) { |
| /* Get start and length of message part of shared memory */ |
| status = cod_get_sym_value(cod_man, MSG_SHARED_BUFFER_BASE_SYM, |
| &ul_msg_base); |
| } |
| if (DSP_SUCCEEDED(status)) { |
| status = cod_get_sym_value(cod_man, MSG_SHARED_BUFFER_LIMIT_SYM, |
| &ul_msg_limit); |
| if (DSP_SUCCEEDED(status)) { |
| if (ul_msg_limit <= ul_msg_base) { |
| status = -EINVAL; |
| } else { |
| /* |
| * Length (bytes) of messaging part of shared |
| * memory. |
| */ |
| ul_msg_length = |
| (ul_msg_limit - ul_msg_base + |
| 1) * hio_mgr->word_size; |
| /* |
| * Total length (bytes) of shared memory: |
| * chnl + msg. |
| */ |
| ul_mem_length = ul_shm_length + ul_msg_length; |
| } |
| } else { |
| status = -EFAULT; |
| } |
| } else { |
| status = -EFAULT; |
| } |
| if (DSP_SUCCEEDED(status)) { |
| #if defined(CONFIG_TIDSPBRIDGE_BACKTRACE) || defined(CONFIG_TIDSPBRIDGE_DEBUG) |
| status = |
| cod_get_sym_value(cod_man, DSP_TRACESEC_END, &shm0_end); |
| #else |
| status = cod_get_sym_value(cod_man, SHM0_SHARED_END_SYM, |
| &shm0_end); |
| #endif |
| if (DSP_FAILED(status)) |
| status = -EFAULT; |
| } |
| if (DSP_SUCCEEDED(status)) { |
| status = |
| cod_get_sym_value(cod_man, DYNEXTBASE, &ul_dyn_ext_base); |
| if (DSP_FAILED(status)) |
| status = -EFAULT; |
| } |
| if (DSP_SUCCEEDED(status)) { |
| status = cod_get_sym_value(cod_man, EXTEND, &ul_ext_end); |
| if (DSP_FAILED(status)) |
| status = -EFAULT; |
| } |
| if (DSP_SUCCEEDED(status)) { |
| /* 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); |
| |
| /* The first MMU TLB entry(TLB_0) in DCD is ShmBase. */ |
| ndx = 0; |
| ul_gpp_pa = host_res->dw_mem_phys[1]; |
| ul_gpp_va = host_res->dw_mem_base[1]; |
| /* This is the virtual uncached ioremapped address!!! */ |
| /* Why can't we directly take the DSPVA from the symbols? */ |
| ul_dsp_va = hio_mgr->ext_proc_info.ty_tlb[0].ul_dsp_virt; |
| ul_seg_size = (shm0_end - ul_dsp_va) * hio_mgr->word_size; |
| ul_seg1_size = |
| (ul_ext_end - ul_dyn_ext_base) * hio_mgr->word_size; |
| /* 4K align */ |
| ul_seg1_size = (ul_seg1_size + 0xFFF) & (~0xFFFUL); |
| /* 64K align */ |
| ul_seg_size = (ul_seg_size + 0xFFFF) & (~0xFFFFUL); |
| ul_pad_size = UL_PAGE_ALIGN_SIZE - ((ul_gpp_pa + ul_seg1_size) % |
| UL_PAGE_ALIGN_SIZE); |
| if (ul_pad_size == UL_PAGE_ALIGN_SIZE) |
| ul_pad_size = 0x0; |
| |
| dev_dbg(bridge, "%s: ul_gpp_pa %x, ul_gpp_va %x, ul_dsp_va %x, " |
| "shm0_end %x, ul_dyn_ext_base %x, ul_ext_end %x, " |
| "ul_seg_size %x ul_seg1_size %x \n", __func__, |
| ul_gpp_pa, ul_gpp_va, ul_dsp_va, shm0_end, |
| ul_dyn_ext_base, ul_ext_end, ul_seg_size, ul_seg1_size); |
| |
| if ((ul_seg_size + ul_seg1_size + ul_pad_size) > |
| host_res->dw_mem_length[1]) { |
| pr_err("%s: shm Error, reserved 0x%x required 0x%x\n", |
| __func__, host_res->dw_mem_length[1], |
| ul_seg_size + ul_seg1_size + ul_pad_size); |
| status = -ENOMEM; |
| } |
| } |
| if (DSP_FAILED(status)) |
| goto func_end; |
| |
| pa_curr = ul_gpp_pa; |
| va_curr = ul_dyn_ext_base * hio_mgr->word_size; |
| gpp_va_curr = ul_gpp_va; |
| num_bytes = ul_seg1_size; |
| |
| /* |
| * 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. |
| */ |
| map_attrs = 0x00000000; |
| map_attrs = DSP_MAPLITTLEENDIAN; |
| map_attrs |= DSP_MAPPHYSICALADDR; |
| map_attrs |= DSP_MAPELEMSIZE32; |
| map_attrs |= DSP_MAPDONOTLOCK; |
| |
| while (num_bytes) { |
| /* |
| * To find the max. page size with which both PA & VA are |
| * aligned. |
| */ |
| all_bits = pa_curr | va_curr; |
| dev_dbg(bridge, "all_bits %x, pa_curr %x, va_curr %x, " |
| "num_bytes %x\n", all_bits, pa_curr, va_curr, |
| num_bytes); |
| for (i = 0; i < 4; i++) { |
| if ((num_bytes >= page_size[i]) && ((all_bits & |
| (page_size[i] - |
| 1)) == 0)) { |
| status = |
| hio_mgr->intf_fxns-> |
| pfn_brd_mem_map(hio_mgr->hbridge_context, |
| pa_curr, va_curr, |
| page_size[i], map_attrs, |
| NULL); |
| if (DSP_FAILED(status)) |
| goto func_end; |
| pa_curr += page_size[i]; |
| va_curr += page_size[i]; |
| gpp_va_curr += page_size[i]; |
| num_bytes -= page_size[i]; |
| /* |
| * Don't try smaller sizes. Hopefully we have |
| * reached an address aligned to a bigger page |
| * size. |
| */ |
| break; |
| } |
| } |
| } |
| pa_curr += ul_pad_size; |
| va_curr += ul_pad_size; |
| gpp_va_curr += ul_pad_size; |
| |
| /* Configure the TLB entries for the next cacheable segment */ |
| num_bytes = ul_seg_size; |
| va_curr = ul_dsp_va * hio_mgr->word_size; |
| while (num_bytes) { |
| /* |
| * To find the max. page size with which both PA & VA are |
| * aligned. |
| */ |
| all_bits = pa_curr | va_curr; |
| dev_dbg(bridge, "all_bits for Seg1 %x, pa_curr %x, " |
| "va_curr %x, num_bytes %x\n", all_bits, pa_curr, |
| va_curr, num_bytes); |
| for (i = 0; i < 4; i++) { |
| if (!(num_bytes >= page_size[i]) || |
| !((all_bits & (page_size[i] - 1)) == 0)) |
| continue; |
| if (ndx < MAX_LOCK_TLB_ENTRIES) { |
| /* |
| * This is the physical address written to |
| * DSP MMU. |
| */ |
| ae_proc[ndx].ul_gpp_pa = pa_curr; |
| /* |
| * This is the virtual uncached ioremapped |
| * address!!! |
| */ |
| ae_proc[ndx].ul_gpp_va = gpp_va_curr; |
| ae_proc[ndx].ul_dsp_va = |
| va_curr / hio_mgr->word_size; |
| ae_proc[ndx].ul_size = page_size[i]; |
| ae_proc[ndx].endianism = HW_LITTLE_ENDIAN; |
| ae_proc[ndx].elem_size = HW_ELEM_SIZE16BIT; |
| ae_proc[ndx].mixed_mode = HW_MMU_CPUES; |
| dev_dbg(bridge, "shm MMU TLB entry PA %x" |
| " VA %x DSP_VA %x Size %x\n", |
| ae_proc[ndx].ul_gpp_pa, |
| ae_proc[ndx].ul_gpp_va, |
| ae_proc[ndx].ul_dsp_va * |
| hio_mgr->word_size, page_size[i]); |
| ndx++; |
| } else { |
| status = |
| hio_mgr->intf_fxns-> |
| pfn_brd_mem_map(hio_mgr->hbridge_context, |
| pa_curr, va_curr, |
| page_size[i], map_attrs, |
| NULL); |
| dev_dbg(bridge, |
| "shm MMU PTE entry PA %x" |
| " VA %x DSP_VA %x Size %x\n", |
| ae_proc[ndx].ul_gpp_pa, |
| ae_proc[ndx].ul_gpp_va, |
| ae_proc[ndx].ul_dsp_va * |
| hio_mgr->word_size, page_size[i]); |
| if (DSP_FAILED(status)) |
| goto func_end; |
| } |
| pa_curr += page_size[i]; |
| va_curr += page_size[i]; |
| gpp_va_curr += page_size[i]; |
| num_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++) { |
| if (hio_mgr->ext_proc_info.ty_tlb[i].ul_gpp_phys == 0) |
| continue; |
| |
| if ((hio_mgr->ext_proc_info.ty_tlb[i].ul_gpp_phys > |
| ul_gpp_pa - 0x100000 |
| && hio_mgr->ext_proc_info.ty_tlb[i].ul_gpp_phys <= |
| ul_gpp_pa + ul_seg_size) |
| || (hio_mgr->ext_proc_info.ty_tlb[i].ul_dsp_virt > |
| ul_dsp_va - 0x100000 / hio_mgr->word_size |
| && hio_mgr->ext_proc_info.ty_tlb[i].ul_dsp_virt <= |
| ul_dsp_va + ul_seg_size / hio_mgr->word_size)) { |
| dev_dbg(bridge, |
| "CDB MMU entry %d conflicts with " |
| "shm.\n\tCDB: GppPa %x, DspVa %x.\n\tSHM: " |
| "GppPa %x, DspVa %x, Bytes %x.\n", i, |
| hio_mgr->ext_proc_info.ty_tlb[i].ul_gpp_phys, |
| hio_mgr->ext_proc_info.ty_tlb[i].ul_dsp_virt, |
| ul_gpp_pa, ul_dsp_va, ul_seg_size); |
| status = -EPERM; |
| } else { |
| if (ndx < MAX_LOCK_TLB_ENTRIES) { |
| ae_proc[ndx].ul_dsp_va = |
| hio_mgr->ext_proc_info.ty_tlb[i]. |
| ul_dsp_virt; |
| ae_proc[ndx].ul_gpp_pa = |
| hio_mgr->ext_proc_info.ty_tlb[i]. |
| ul_gpp_phys; |
| ae_proc[ndx].ul_gpp_va = 0; |
| /* 1 MB */ |
| ae_proc[ndx].ul_size = 0x100000; |
| dev_dbg(bridge, "shm MMU entry PA %x " |
| "DSP_VA 0x%x\n", ae_proc[ndx].ul_gpp_pa, |
| ae_proc[ndx].ul_dsp_va); |
| ndx++; |
| } else { |
| status = hio_mgr->intf_fxns->pfn_brd_mem_map |
| (hio_mgr->hbridge_context, |
| hio_mgr->ext_proc_info.ty_tlb[i]. |
| ul_gpp_phys, |
| hio_mgr->ext_proc_info.ty_tlb[i]. |
| ul_dsp_virt, 0x100000, map_attrs, |
| NULL); |
| } |
| } |
| if (DSP_FAILED(status)) |
| goto func_end; |
| } |
| |
| map_attrs = 0x00000000; |
| map_attrs = DSP_MAPLITTLEENDIAN; |
| map_attrs |= DSP_MAPPHYSICALADDR; |
| map_attrs |= DSP_MAPELEMSIZE32; |
| map_attrs |= DSP_MAPDONOTLOCK; |
| |
| /* Map the L4 peripherals */ |
| i = 0; |
| while (l4_peripheral_table[i].phys_addr) { |
| status = hio_mgr->intf_fxns->pfn_brd_mem_map |
| (hio_mgr->hbridge_context, l4_peripheral_table[i].phys_addr, |
| l4_peripheral_table[i].dsp_virt_addr, HW_PAGE_SIZE4KB, |
| map_attrs, NULL); |
| if (DSP_FAILED(status)) |
| goto func_end; |
| i++; |
| } |
| |
| for (i = ndx; i < BRDIOCTL_NUMOFMMUTLB; i++) { |
| ae_proc[i].ul_dsp_va = 0; |
| ae_proc[i].ul_gpp_pa = 0; |
| ae_proc[i].ul_gpp_va = 0; |
| ae_proc[i].ul_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].ul_gpp_phys = |
| (ul_gpp_va + ul_seg1_size + ul_pad_size); |
| |
| /* |
| * Need shm Phys addr. IO supports only one DSP for now: |
| * num_procs = 1. |
| */ |
| if (!hio_mgr->ext_proc_info.ty_tlb[0].ul_gpp_phys || num_procs != 1) { |
| status = -EFAULT; |
| goto func_end; |
| } else { |
| if (ae_proc[0].ul_dsp_va > ul_shm_base) { |
| status = -EPERM; |
| goto func_end; |
| } |
| /* ul_shm_base may not be at ul_dsp_va address */ |
| ul_shm_base_offset = (ul_shm_base - ae_proc[0].ul_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->pfn_dev_cntrl(hio_mgr->hbridge_context, |
| BRDIOCTL_SETMMUCONFIG, |
| ae_proc); |
| if (DSP_FAILED(status)) |
| goto func_end; |
| ul_shm_base = hio_mgr->ext_proc_info.ty_tlb[0].ul_gpp_phys; |
| ul_shm_base += ul_shm_base_offset; |
| ul_shm_base = (u32) MEM_LINEAR_ADDRESS((void *)ul_shm_base, |
| ul_mem_length); |
| if (ul_shm_base == 0) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| /* Register SM */ |
| status = |
| register_shm_segs(hio_mgr, cod_man, ae_proc[0].ul_gpp_pa); |
| } |
| |
| hio_mgr->shared_mem = (struct shm *)ul_shm_base; |
| hio_mgr->input = (u8 *) hio_mgr->shared_mem + sizeof(struct shm); |
| hio_mgr->output = hio_mgr->input + (ul_shm_length - |
| sizeof(struct shm)) / 2; |
| hio_mgr->usm_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 |
| + ul_shm_length); |
| 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 + |
| ul_msg_length / 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) || defined(CONFIG_TIDSPBRIDGE_DEBUG) |
| /* Get the start address of trace buffer */ |
| status = cod_get_sym_value(cod_man, SYS_PUTCBEG, |
| &hio_mgr->ul_trace_buffer_begin); |
| if (DSP_FAILED(status)) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| |
| hio_mgr->ul_gpp_read_pointer = hio_mgr->ul_trace_buffer_begin = |
| (ul_gpp_va + ul_seg1_size + ul_pad_size) + |
| (hio_mgr->ul_trace_buffer_begin - ul_dsp_va); |
| /* Get the end address of trace buffer */ |
| status = cod_get_sym_value(cod_man, SYS_PUTCEND, |
| &hio_mgr->ul_trace_buffer_end); |
| if (DSP_FAILED(status)) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| hio_mgr->ul_trace_buffer_end = |
| (ul_gpp_va + ul_seg1_size + ul_pad_size) + |
| (hio_mgr->ul_trace_buffer_end - ul_dsp_va); |
| /* Get the current address of DSP write pointer */ |
| status = cod_get_sym_value(cod_man, BRIDGE_SYS_PUTC_CURRENT, |
| &hio_mgr->ul_trace_buffer_current); |
| if (DSP_FAILED(status)) { |
| status = -EFAULT; |
| goto func_end; |
| } |
| hio_mgr->ul_trace_buffer_current = |
| (ul_gpp_va + ul_seg1_size + ul_pad_size) + |
| (hio_mgr->ul_trace_buffer_current - ul_dsp_va); |
| /* Calculate the size of trace buffer */ |
| kfree(hio_mgr->pmsg); |
| hio_mgr->pmsg = kmalloc(((hio_mgr->ul_trace_buffer_end - |
| hio_mgr->ul_trace_buffer_begin) * |
| hio_mgr->word_size) + 2, GFP_KERNEL); |
| if (!hio_mgr->pmsg) |
| status = -ENOMEM; |
| |
| hio_mgr->ul_dsp_va = ul_dsp_va; |
| hio_mgr->ul_gpp_va = (ul_gpp_va + ul_seg1_size + ul_pad_size); |
| |
| #endif |
| func_end: |
| 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->usm_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 ulChnl) |
| { |
| 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 */ |
| IO_AND_VALUE(pio_mgr->hbridge_context, struct shm, sm, host_free_mask, |
| (~(1 << ulChnl))); |
| |
| sm_interrupt_dsp(pio_mgr->hbridge_context, MBX_PCPY_CLASS); |
| func_end: |
| return; |
| } |
| |
| /* |
| * ======== io_dispatch_chnl ======== |
| * Proc-copy chanl dispatch. |
| */ |
| static void io_dispatch_chnl(IN struct io_mgr *pio_mgr, |
| IN OUT struct chnl_object *pchnl, u8 io_mode) |
| { |
| if (!pio_mgr) |
| goto func_end; |
| |
| /* See if there is any data available for transfer */ |
| if (io_mode != IO_SERVICE) |
| goto func_end; |
| |
| /* Any channel will do for this mode */ |
| input_chnl(pio_mgr, pchnl, io_mode); |
| output_chnl(pio_mgr, pchnl, io_mode); |
| func_end: |
| return; |
| } |
| |
| /* |
| * ======== io_dispatch_msg ======== |
| * Performs I/O dispatch on message queues. |
| */ |
| static void io_dispatch_msg(IN struct io_mgr *pio_mgr, struct msg_mgr *hmsg_mgr) |
| { |
| if (!pio_mgr) |
| goto func_end; |
| |
| /* We are performing both input and output processing. */ |
| input_msg(pio_mgr, hmsg_mgr); |
| output_msg(pio_mgr, hmsg_mgr); |
| 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-> |
| pfn_dev_cntrl(pio_mgr->hbridge_context, |
| BRDIOCTL_PWR_HIBERNATE, parg); |
| if (DSP_FAILED(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-> |
| pfn_dev_cntrl(pio_mgr->hbridge_context, |
| BRDIOCTL_CONSTRAINT_REQUEST, parg); |
| if (DSP_FAILED(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-> |
| pfn_dev_cntrl(pio_mgr->hbridge_context, |
| BRDIOCTL_CLK_CTRL, parg); |
| if (DSP_FAILED(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(IN OUT 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->hchnl_mgr; |
| dev_get_msg_mgr(pio_mgr->hdev_obj, &msg_mgr_obj); |
| dev_get_deh_mgr(pio_mgr->hdev_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); |
| } |
| } |
| io_dispatch_chnl(pio_mgr, NULL, IO_SERVICE); |
| #ifdef CHNL_MESSAGES |
| if (msg_mgr_obj) |
| io_dispatch_msg(pio_mgr, msg_mgr_obj); |
| #endif |
| #ifdef CONFIG_TIDSPBRIDGE_DEBUG |
| 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. |
| */ |
| void io_mbox_msg(u32 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; |
| |
| pio_mgr->intr_val = (u16)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; |
| } |
| |
| /* |
| * ======== io_request_chnl ======== |
| * Purpose: |
| * Request chanenel I/O from the DSP. Sets flags in shared memory, then |
| * interrupts the DSP. |
| */ |
| void io_request_chnl(struct io_mgr *pio_mgr, struct chnl_object *pchnl, |
| u8 io_mode, OUT u16 *mbx_val) |
| { |
| struct chnl_mgr *chnl_mgr_obj; |
| struct shm *sm; |
| |
| if (!pchnl || !mbx_val) |
| goto func_end; |
| chnl_mgr_obj = pio_mgr->hchnl_mgr; |
| sm = pio_mgr->shared_mem; |
| if (io_mode == IO_INPUT) { |
| /* |
| * Assertion fires if CHNL_AddIOReq() called on a stream |
| * which was cancelled, or attached to a dead board. |
| */ |
| DBC_ASSERT((pchnl->dw_state == CHNL_STATEREADY) || |
| (pchnl->dw_state == CHNL_STATEEOS)); |
| /* Indicate to the DSP we have a buffer available for input */ |
| IO_OR_VALUE(pio_mgr->hbridge_context, struct shm, sm, |
| host_free_mask, (1 << pchnl->chnl_id)); |
| *mbx_val = MBX_PCPY_CLASS; |
| } else if (io_mode == IO_OUTPUT) { |
| /* |
| * This assertion fails if CHNL_AddIOReq() was called on a |
| * stream which was cancelled, or attached to a dead board. |
| */ |
| DBC_ASSERT((pchnl->dw_state & ~CHNL_STATEEOS) == |
| CHNL_STATEREADY); |
| /* |
| * Record the fact that we have a buffer available for |
| * output. |
| */ |
| chnl_mgr_obj->dw_output_mask |= (1 << pchnl->chnl_id); |
| } else { |
| DBC_ASSERT(io_mode); /* Shouldn't get here. */ |
| } |
| func_end: |
| return; |
| } |
| |
| /* |
| * ======== iosm_schedule ======== |
| * Schedule DPC for IO. |
| */ |
| void iosm_schedule(struct io_mgr *pio_mgr) |
| { |
| unsigned long flags; |
| |
| if (!pio_mgr) |
| return; |
| |
| /* Increment count of DPC's pending. */ |
| spin_lock_irqsave(&pio_mgr->dpc_lock, flags); |
| pio_mgr->dpc_req++; |
| spin_unlock_irqrestore(&pio_mgr->dpc_lock, flags); |
| |
| /* Schedule DPC */ |
| tasklet_schedule(&pio_mgr->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->dw_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->dw_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->hchnl_mgr; |
| |
| /* Attempt to perform input */ |
| if (!IO_GET_VALUE(pio_mgr->hbridge_context, struct shm, sm, input_full)) |
| goto func_end; |
| |
| bytes = |
| IO_GET_VALUE(pio_mgr->hbridge_context, struct shm, sm, |
| input_size) * chnl_mgr_obj->word_size; |
| chnl_id = IO_GET_VALUE(pio_mgr->hbridge_context, struct shm, |
| sm, input_id); |
| dw_arg = IO_GET_LONG(pio_mgr->hbridge_context, struct shm, sm, arg); |
| if (chnl_id >= CHNL_MAXCHANNELS) { |
| /* Shouldn't be here: would indicate corrupted shm. */ |
| DBC_ASSERT(chnl_id); |
| goto func_end; |
| } |
| pchnl = chnl_mgr_obj->ap_channel[chnl_id]; |
| if ((pchnl != NULL) && CHNL_IS_INPUT(pchnl->chnl_mode)) { |
| if ((pchnl->dw_state & ~CHNL_STATEEOS) == CHNL_STATEREADY) { |
| if (!pchnl->pio_requests) |
| goto func_end; |
| /* Get the I/O request, and attempt a transfer */ |
| chnl_packet_obj = (struct chnl_irp *) |
| lst_get_head(pchnl->pio_requests); |
| if (chnl_packet_obj) { |
| pchnl->cio_reqs--; |
| if (pchnl->cio_reqs < 0) |
| goto func_end; |
| /* |
| * Ensure we don't overflow the client's |
| * buffer. |
| */ |
| bytes = min(bytes, chnl_packet_obj->byte_size); |
| /* Transfer buffer from DSP side */ |
| bytes = read_data(pio_mgr->hbridge_context, |
| chnl_packet_obj->host_sys_buf, |
| pio_mgr->input, bytes); |
| pchnl->bytes_moved += bytes; |
| chnl_packet_obj->byte_size = bytes; |
| chnl_packet_obj->dw_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->dw_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->dw_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 (!pchnl->pio_requests) |
| goto func_end; |
| if (LST_IS_EMPTY(pchnl->pio_requests)) { |
| IO_AND_VALUE(pio_mgr->hbridge_context, |
| struct shm, sm, |
| host_free_mask, |
| ~(1 << 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 */ |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct shm, sm, |
| input_full, 0); |
| sm_interrupt_dsp(pio_mgr->hbridge_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 = |
| IO_GET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, msg_ctr_obj, |
| buf_empty); |
| num_msgs = |
| IO_GET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, msg_ctr_obj, |
| size); |
| if (input_empty) |
| goto func_end; |
| |
| 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.dw_cmd); |
| msg.msg.dw_cmd = |
| read_ext32_bit_dsp_data(pio_mgr->hbridge_context, addr); |
| addr = (u32) &(((struct msg_dspmsg *)msg_input)->msg.dw_arg1); |
| msg.msg.dw_arg1 = |
| read_ext32_bit_dsp_data(pio_mgr->hbridge_context, addr); |
| addr = (u32) &(((struct msg_dspmsg *)msg_input)->msg.dw_arg2); |
| msg.msg.dw_arg2 = |
| read_ext32_bit_dsp_data(pio_mgr->hbridge_context, addr); |
| addr = (u32) &(((struct msg_dspmsg *)msg_input)->msgq_id); |
| msg.msgq_id = |
| read_ext32_bit_dsp_data(pio_mgr->hbridge_context, addr); |
| msg_input += sizeof(struct msg_dspmsg); |
| if (!hmsg_mgr->queue_list) |
| goto func_end; |
| |
| /* Determine which queue to put the message in */ |
| msg_queue_obj = |
| (struct msg_queue *)lst_first(hmsg_mgr->queue_list); |
| dev_dbg(bridge, "input msg: dw_cmd=0x%x dw_arg1=0x%x " |
| "dw_arg2=0x%x msgq_id=0x%x \n", msg.msg.dw_cmd, |
| msg.msg.dw_arg1, msg.msg.dw_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. |
| */ |
| while (msg_queue_obj != NULL) { |
| if (msg.msgq_id == msg_queue_obj->msgq_id) { |
| /* Found it */ |
| if (msg.msg.dw_cmd == RMS_EXITACK) { |
| /* |
| * Call the node exit notification. |
| * The exit message does not get |
| * queued. |
| */ |
| (*hmsg_mgr->on_exit) ((void *) |
| msg_queue_obj->arg, |
| msg.msg.dw_arg1); |
| } else { |
| /* |
| * Not an exit acknowledgement, queue |
| * the message. |
| */ |
| if (!msg_queue_obj->msg_free_list) |
| goto func_end; |
| pmsg = (struct msg_frame *)lst_get_head |
| (msg_queue_obj->msg_free_list); |
| if (msg_queue_obj->msg_used_list |
| && pmsg) { |
| pmsg->msg_data = msg; |
| lst_put_tail |
| (msg_queue_obj->msg_used_list, |
| (struct list_head *)pmsg); |
| ntfy_notify |
| (msg_queue_obj->ntfy_obj, |
| DSP_NODEMESSAGEREADY); |
| sync_set_event |
| (msg_queue_obj->sync_event); |
| } else { |
| /* |
| * No free frame to copy the |
| * message into. |
| */ |
| pr_err("%s: no free msg frames," |
| " discarding msg\n", |
| __func__); |
| } |
| } |
| break; |
| } |
| |
| if (!hmsg_mgr->queue_list || !msg_queue_obj) |
| goto func_end; |
| msg_queue_obj = |
| (struct msg_queue *)lst_next(hmsg_mgr->queue_list, |
| (struct list_head *) |
| msg_queue_obj); |
| } |
| } |
| /* Set the post SWI flag */ |
| if (num_msgs > 0) { |
| /* Tell the DSP we've read the messages */ |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, |
| msg_ctr_obj, buf_empty, true); |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, |
| msg_ctr_obj, post_swi, true); |
| sm_interrupt_dsp(pio_mgr->hbridge_context, MBX_PCPY_CLASS); |
| } |
| func_end: |
| return; |
| } |
| |
| /* |
| * ======== 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 || |
| !pchnl->pio_completions || !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 = LST_IS_EMPTY(pchnl->pio_completions); |
| /* Enqueue the IO completion info for the client */ |
| lst_put_tail(pchnl->pio_completions, |
| (struct list_head *)chnl_packet_obj); |
| 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->hchnl_mgr; |
| sm = pio_mgr->shared_mem; |
| /* Attempt to perform output */ |
| if (IO_GET_VALUE(pio_mgr->hbridge_context, struct shm, sm, output_full)) |
| goto func_end; |
| |
| if (pchnl && !((pchnl->dw_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 = IO_GET_VALUE(pio_mgr->hbridge_context, struct shm, sm, |
| dsp_free_mask); |
| chnl_id = |
| find_ready_output(chnl_mgr_obj, pchnl, |
| (chnl_mgr_obj->dw_output_mask & dw_dsp_f_mask)); |
| if (chnl_id == OUTPUTNOTREADY) |
| goto func_end; |
| |
| pchnl = chnl_mgr_obj->ap_channel[chnl_id]; |
| if (!pchnl || !pchnl->pio_requests) { |
| /* Shouldn't get here */ |
| goto func_end; |
| } |
| /* Get the I/O request, and attempt a transfer */ |
| chnl_packet_obj = (struct chnl_irp *)lst_get_head(pchnl->pio_requests); |
| if (!chnl_packet_obj) |
| goto func_end; |
| |
| pchnl->cio_reqs--; |
| if (pchnl->cio_reqs < 0 || !pchnl->pio_requests) |
| goto func_end; |
| |
| /* Record fact that no more I/O buffers available */ |
| if (LST_IS_EMPTY(pchnl->pio_requests)) |
| chnl_mgr_obj->dw_output_mask &= ~(1 << chnl_id); |
| |
| /* Transfer buffer to DSP side */ |
| chnl_packet_obj->byte_size = |
| write_data(pio_mgr->hbridge_context, pio_mgr->output, |
| chnl_packet_obj->host_sys_buf, min(pio_mgr->usm_buf_size, |
| chnl_packet_obj->byte_size)); |
| pchnl->bytes_moved += chnl_packet_obj->byte_size; |
| /* Write all 32 bits of arg */ |
| IO_SET_LONG(pio_mgr->hbridge_context, struct shm, sm, arg, |
| chnl_packet_obj->dw_arg); |
| #if _CHNL_WORDSIZE == 2 |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct shm, sm, output_id, |
| (u16) chnl_id); |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct shm, sm, output_size, |
| (u16) (chnl_packet_obj->byte_size + |
| (chnl_mgr_obj->word_size - |
| 1)) / (u16) chnl_mgr_obj->word_size); |
| #else |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct shm, sm, output_id, |
| chnl_id); |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct shm, sm, output_size, |
| (chnl_packet_obj->byte_size + |
| (chnl_mgr_obj->word_size - 1)) / chnl_mgr_obj->word_size); |
| #endif |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct shm, sm, output_full, 1); |
| /* Indicate to the DSP we have written the output */ |
| sm_interrupt_dsp(pio_mgr->hbridge_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; |
| u8 *msg_output; |
| struct msg_frame *pmsg; |
| struct msg_ctrl *msg_ctr_obj; |
| u32 output_empty; |
| u32 val; |
| u32 addr; |
| |
| msg_ctr_obj = pio_mgr->msg_output_ctrl; |
| |
| /* Check if output has been cleared */ |
| output_empty = |
| IO_GET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, msg_ctr_obj, |
| buf_empty); |
| if (output_empty) { |
| num_msgs = (hmsg_mgr->msgs_pending > hmsg_mgr->max_msgs) ? |
| hmsg_mgr->max_msgs : hmsg_mgr->msgs_pending; |
| msg_output = pio_mgr->msg_output; |
| /* Copy num_msgs messages into shared memory */ |
| for (i = 0; i < num_msgs; i++) { |
| if (!hmsg_mgr->msg_used_list) { |
| pmsg = NULL; |
| goto func_end; |
| } else { |
| pmsg = (struct msg_frame *) |
| lst_get_head(hmsg_mgr->msg_used_list); |
| } |
| if (pmsg != NULL) { |
| val = (pmsg->msg_data).msgq_id; |
| addr = (u32) &(((struct msg_dspmsg *) |
| msg_output)->msgq_id); |
| write_ext32_bit_dsp_data( |
| pio_mgr->hbridge_context, addr, val); |
| val = (pmsg->msg_data).msg.dw_cmd; |
| addr = (u32) &((((struct msg_dspmsg *) |
| msg_output)->msg).dw_cmd); |
| write_ext32_bit_dsp_data( |
| pio_mgr->hbridge_context, addr, val); |
| val = (pmsg->msg_data).msg.dw_arg1; |
| addr = (u32) &((((struct msg_dspmsg *) |
| msg_output)->msg).dw_arg1); |
| write_ext32_bit_dsp_data( |
| pio_mgr->hbridge_context, addr, val); |
| val = (pmsg->msg_data).msg.dw_arg2; |
| addr = (u32) &((((struct msg_dspmsg *) |
| msg_output)->msg).dw_arg2); |
| write_ext32_bit_dsp_data( |
| pio_mgr->hbridge_context, addr, val); |
| msg_output += sizeof(struct msg_dspmsg); |
| if (!hmsg_mgr->msg_free_list) |
| goto func_end; |
| lst_put_tail(hmsg_mgr->msg_free_list, |
| (struct list_head *)pmsg); |
| sync_set_event(hmsg_mgr->sync_event); |
| } |
| } |
| |
| if (num_msgs > 0) { |
| hmsg_mgr->msgs_pending -= num_msgs; |
| #if _CHNL_WORDSIZE == 2 |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, |
| msg_ctr_obj, size, (u16) num_msgs); |
| #else |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, |
| msg_ctr_obj, size, num_msgs); |
| #endif |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, |
| msg_ctr_obj, buf_empty, false); |
| /* Set the post SWI flag */ |
| IO_SET_VALUE(pio_mgr->hbridge_context, struct msg_ctrl, |
| msg_ctr_obj, post_swi, true); |
| /* Tell the DSP we have written the output. */ |
| sm_interrupt_dsp(pio_mgr->hbridge_context, |
| MBX_PCPY_CLASS); |
| } |
| } |
| func_end: |
| return; |
| } |
| |
| /* |
| * ======== 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 (DSP_SUCCEEDED(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 (DSP_SUCCEEDED(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 (DSP_SUCCEEDED(status)) { |
| status = dev_get_cmm_mgr(hio_mgr->hdev_obj, &hio_mgr->hcmm_mgr); |
| if (DSP_SUCCEEDED(status)) { |
| status = cmm_un_register_gppsm_seg(hio_mgr->hcmm_mgr, |
| CMM_ALLSEGMENTS); |
| } |
| } |
| /* Register new SM region(s) */ |
| if (DSP_SUCCEEDED(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].ul_gpp_phys; |
| /* Get size in bytes */ |
| ul_dsp_virt = |
| hio_mgr->ext_proc_info.ty_tlb[0].ul_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->hcmm_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; |
| } |
| |
| /* |
| * ======== read_data ======== |
| * Copies buffers from the shared memory to the host buffer. |
| */ |
| static u32 read_data(struct bridge_dev_context *dev_ctxt, void *dest, |
| void *src, u32 usize) |
| { |
| memcpy(dest, src, usize); |
| return usize; |
| } |
| |
| /* |
| * ======== write_data ======== |
| * Copies buffers from the host side buffer to the shared memory. |
| */ |
| static u32 write_data(struct bridge_dev_context *dev_ctxt, void *dest, |
| void *src, u32 usize) |
| { |
| memcpy(dest, src, usize); |
| return usize; |
| } |
| |
| /* ZCPY IO routines. */ |
| void io_intr_dsp2(IN struct io_mgr *pio_mgr, IN u16 mb_val) |
| { |
| sm_interrupt_dsp(pio_mgr->hbridge_context, mb_val); |
| } |
| |
| /* |
| * ======== 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(IN struct io_mgr *hio_mgr, |
| OUT struct dsp_procloadstat *proc_lstat) |
| { |
| 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; |
| } |
| |
| void io_sm_init(void) |
| { |
| /* Do nothing */ |
| } |
| |
| #if defined(CONFIG_TIDSPBRIDGE_BACKTRACE) || defined(CONFIG_TIDSPBRIDGE_DEBUG) |
| 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->ul_trace_buffer_current); |
| ul_gpp_cur_pointer = |
| hio_mgr->ul_gpp_va + (ul_gpp_cur_pointer - |
| hio_mgr->ul_dsp_va); |
| |
| /* No new debug messages available yet */ |
| if (ul_gpp_cur_pointer == hio_mgr->ul_gpp_read_pointer) { |
| break; |
| } else if (ul_gpp_cur_pointer > hio_mgr->ul_gpp_read_pointer) { |
| /* Continuous data */ |
| ul_new_message_length = |
| ul_gpp_cur_pointer - hio_mgr->ul_gpp_read_pointer; |
| |
| memcpy(hio_mgr->pmsg, |
| (char *)hio_mgr->ul_gpp_read_pointer, |
| ul_new_message_length); |
| hio_mgr->pmsg[ul_new_message_length] = '\0'; |
| /* |
| * Advance the GPP trace pointer to DSP current |
| * pointer. |
| */ |
| hio_mgr->ul_gpp_read_pointer += ul_new_message_length; |
| /* Print the trace messages */ |
| pr_info("DSPTrace: %s\n", hio_mgr->pmsg); |
| } else if (ul_gpp_cur_pointer < hio_mgr->ul_gpp_read_pointer) { |
| /* Handle trace buffer wraparound */ |
| memcpy(hio_mgr->pmsg, |
| (char *)hio_mgr->ul_gpp_read_pointer, |
| hio_mgr->ul_trace_buffer_end - |
| hio_mgr->ul_gpp_read_pointer); |
| ul_new_message_length = |
| ul_gpp_cur_pointer - hio_mgr->ul_trace_buffer_begin; |
| memcpy(&hio_mgr->pmsg[hio_mgr->ul_trace_buffer_end - |
| hio_mgr->ul_gpp_read_pointer], |
| (char *)hio_mgr->ul_trace_buffer_begin, |
| ul_new_message_length); |
| hio_mgr->pmsg[hio_mgr->ul_trace_buffer_end - |
| hio_mgr->ul_gpp_read_pointer + |
| ul_new_message_length] = '\0'; |
| /* |
| * Advance the GPP trace pointer to DSP current |
| * pointer. |
| */ |
| hio_mgr->ul_gpp_read_pointer = |
| hio_mgr->ul_trace_buffer_begin + |
| ul_new_message_length; |
| /* Print the trace messages */ |
| pr_info("DSPTrace: %s\n", hio_mgr->pmsg); |
| } |
| } |
| } |
| #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->hdev_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 (DSP_SUCCEEDED(status)) |
| status = |
| cod_get_sym_value(cod_mgr, COD_TRACEEND, &ul_trace_end); |
| |
| if (DSP_SUCCEEDED(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 (DSP_FAILED(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 (DSP_FAILED(status)) |
| goto func_end; |
| |
| psz_buf = kzalloc(ul_num_bytes + 2, GFP_ATOMIC); |
| if (psz_buf != NULL) { |
| /* Read trace buffer data */ |
| status = (*intf_fxns->pfn_brd_read)(pbridge_context, |
| (u8 *)psz_buf, (u32)ul_trace_begin, |
| ul_num_bytes, 0); |
| |
| if (DSP_FAILED(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->pfn_brd_read)(pbridge_context, |
| (u8 *)&trace_cur_pos, (u32)trace_cur_pos, |
| 4, 0); |
| if (DSP_FAILED(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 (DSP_FAILED(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->hdev_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 (DSP_SUCCEEDED(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 (DSP_SUCCEEDED(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 (DSP_FAILED(status)) |
| pr_debug("%s: Failed on cod_get_sym_value.\n", |
| __func__); |
| } |
| if (DSP_SUCCEEDED(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 (DSP_SUCCEEDED(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->pfn_brd_read)(bridge_context, |
| (u8 *)&mmu_fault_dbg_info, (u32)trace_begin, |
| sizeof(mmu_fault_dbg_info), 0); |
| |
| if (DSP_FAILED(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 (DSP_SUCCEEDED(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->pfn_brd_read)(bridge_context, |
| (u8 *)buffer, (u32)trace_begin, |
| total_size, 0); |
| if (DSP_FAILED(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 (DSP_FAILED(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->hdev_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 (DSP_FAILED(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 (DSP_FAILED(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->pfn_brd_read)(bridge_context, (u8 *) &modules_hdr, |
| (u32) module_dsp_addr, sizeof(modules_hdr), 0); |
| |
| if (DSP_FAILED(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->pfn_brd_read)(bridge_context, |
| (u8 *)module_struct, module_dsp_addr, module_size, 0); |
| |
| if (DSP_FAILED(status)) { |
| pr_debug( |
| "%s: Failed to read dll_module stuct 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 immedialty 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 |