/* * ue_deh.c * * DSP-BIOS Bridge driver support functions for TI OMAP processors. * * Implements upper edge DSP exception handling (DEH) functions. * * Copyright (C) 2005-2006 Texas Instruments, Inc. * Copyright (C) 2010 Felipe Contreras * * 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. */ #include #include #include #include #include #include #include "_tiomap.h" #include "_deh.h" #include #include #include #define MMU_CNTL_TWL_EN (1 << 2) static void mmu_fault_dpc(unsigned long data) { struct deh_mgr *deh = (void *)data; if (!deh) return; bridge_deh_notify(deh, DSP_MMUFAULT, 0); } int mmu_fault_isr(struct iommu *mmu) { struct deh_mgr *dm; dev_get_deh_mgr(dev_get_first(), &dm); if (!dm) return -EPERM; iommu_write_reg(mmu, 0, MMU_IRQENABLE); tasklet_schedule(&dm->dpc_tasklet); return 0; } int bridge_deh_create(struct deh_mgr **ret_deh, struct dev_object *hdev_obj) { int status; struct deh_mgr *deh; struct bridge_dev_context *hbridge_context = NULL; /* Message manager will be created when a file is loaded, since * size of message buffer in shared memory is configurable in * the base image. */ /* Get Bridge context info. */ dev_get_bridge_context(hdev_obj, &hbridge_context); /* Allocate IO manager object: */ deh = kzalloc(sizeof(*deh), GFP_KERNEL); if (!deh) { status = -ENOMEM; goto err; } /* Create an NTFY object to manage notifications */ deh->ntfy_obj = kmalloc(sizeof(struct ntfy_object), GFP_KERNEL); if (!deh->ntfy_obj) { status = -ENOMEM; goto err; } ntfy_init(deh->ntfy_obj); /* Create a MMUfault DPC */ tasklet_init(&deh->dpc_tasklet, mmu_fault_dpc, (u32) deh); /* Fill in context structure */ deh->hbridge_context = hbridge_context; *ret_deh = deh; return 0; err: bridge_deh_destroy(deh); *ret_deh = NULL; return status; } int bridge_deh_destroy(struct deh_mgr *deh) { if (!deh) return -EFAULT; /* If notification object exists, delete it */ if (deh->ntfy_obj) { ntfy_delete(deh->ntfy_obj); kfree(deh->ntfy_obj); } /* Free DPC object */ tasklet_kill(&deh->dpc_tasklet); /* Deallocate the DEH manager object */ kfree(deh); return 0; } int bridge_deh_register_notify(struct deh_mgr *deh, u32 event_mask, u32 notify_type, struct dsp_notification *hnotification) { if (!deh) return -EFAULT; if (event_mask) return ntfy_register(deh->ntfy_obj, hnotification, event_mask, notify_type); else return ntfy_unregister(deh->ntfy_obj, hnotification); } #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE static void mmu_fault_print_stack(struct bridge_dev_context *dev_context) { void *dummy_addr; u32 fa, tmp; struct iotlb_entry e; struct iommu *mmu = dev_context->dsp_mmu; dummy_addr = (void *)__get_free_page(GFP_ATOMIC); /* * Before acking the MMU fault, let's make sure MMU can only * access entry #0. Then add a new entry so that the DSP OS * can continue in order to dump the stack. */ tmp = iommu_read_reg(mmu, MMU_CNTL); tmp &= ~MMU_CNTL_TWL_EN; iommu_write_reg(mmu, tmp, MMU_CNTL); fa = iommu_read_reg(mmu, MMU_FAULT_AD); e.da = fa & PAGE_MASK; e.pa = virt_to_phys(dummy_addr); e.valid = 1; e.prsvd = 1; e.pgsz = IOVMF_PGSZ_4K & MMU_CAM_PGSZ_MASK; e.endian = MMU_RAM_ENDIAN_LITTLE; e.elsz = MMU_RAM_ELSZ_32; e.mixed = 0; load_iotlb_entry(dev_context->dsp_mmu, &e); dsp_clk_enable(DSP_CLK_GPT8); dsp_gpt_wait_overflow(DSP_CLK_GPT8, 0xfffffffe); /* Clear MMU interrupt */ tmp = iommu_read_reg(mmu, MMU_IRQSTATUS); iommu_write_reg(mmu, tmp, MMU_IRQSTATUS); dump_dsp_stack(dev_context); dsp_clk_disable(DSP_CLK_GPT8); iopgtable_clear_entry(mmu, fa); free_page((unsigned long)dummy_addr); } #endif static inline const char *event_to_string(int event) { switch (event) { case DSP_SYSERROR: return "DSP_SYSERROR"; break; case DSP_MMUFAULT: return "DSP_MMUFAULT"; break; case DSP_PWRERROR: return "DSP_PWRERROR"; break; case DSP_WDTOVERFLOW: return "DSP_WDTOVERFLOW"; break; default: return "unkown event"; break; } } void bridge_deh_notify(struct deh_mgr *deh, int event, int info) { struct bridge_dev_context *dev_context; const char *str = event_to_string(event); u32 fa; if (!deh) return; dev_dbg(bridge, "%s: device exception", __func__); dev_context = deh->hbridge_context; switch (event) { case DSP_SYSERROR: dev_err(bridge, "%s: %s, info=0x%x", __func__, str, info); #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE dump_dl_modules(dev_context); dump_dsp_stack(dev_context); #endif break; case DSP_MMUFAULT: fa = iommu_read_reg(dev_context->dsp_mmu, MMU_FAULT_AD); dev_err(bridge, "%s: %s, addr=0x%x", __func__, str, fa); #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE print_dsp_trace_buffer(dev_context); dump_dl_modules(dev_context); mmu_fault_print_stack(dev_context); #endif break; default: dev_err(bridge, "%s: %s", __func__, str); break; } /* Filter subsequent notifications when an error occurs */ if (dev_context->dw_brd_state != BRD_ERROR) { ntfy_notify(deh->ntfy_obj, event); #ifdef CONFIG_TIDSPBRIDGE_RECOVERY bridge_recover_schedule(); #endif } /* Set the Board state as ERROR */ dev_context->dw_brd_state = BRD_ERROR; /* Disable all the clocks that were enabled by DSP */ dsp_clock_disable_all(dev_context->dsp_per_clks); /* * Avoid the subsequent WDT if it happens once, * also if fatal error occurs. */ dsp_wdt_enable(false); }