| /* |
| * 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 |