Commit | Line | Data |
---|---|---|
999e07d6 ORL |
1 | /* |
2 | * ue_deh.c | |
3 | * | |
4 | * DSP-BIOS Bridge driver support functions for TI OMAP processors. | |
5 | * | |
6 | * Implements upper edge DSP exception handling (DEH) functions. | |
7 | * | |
8 | * Copyright (C) 2005-2006 Texas Instruments, Inc. | |
94e7e526 | 9 | * Copyright (C) 2010 Felipe Contreras |
999e07d6 ORL |
10 | * |
11 | * This package is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License version 2 as | |
13 | * published by the Free Software Foundation. | |
14 | * | |
15 | * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR | |
16 | * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED | |
17 | * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. | |
18 | */ | |
19 | ||
96904b06 FC |
20 | #include <linux/kernel.h> |
21 | #include <linux/interrupt.h> | |
22 | #include <plat/dmtimer.h> | |
999e07d6 | 23 | |
999e07d6 | 24 | #include <dspbridge/dbdefs.h> |
999e07d6 | 25 | #include <dspbridge/dspdeh.h> |
999e07d6 | 26 | #include <dspbridge/dev.h> |
999e07d6 ORL |
27 | #include "_tiomap.h" |
28 | #include "_deh.h" | |
96904b06 | 29 | |
999e07d6 | 30 | #include <dspbridge/io_sm.h> |
96904b06 FC |
31 | #include <dspbridge/drv.h> |
32 | #include <dspbridge/wdt.h> | |
999e07d6 | 33 | |
6c4c899e | 34 | static u32 fault_addr; |
f5bd96bb FC |
35 | |
36 | static void mmu_fault_dpc(unsigned long data) | |
37 | { | |
38 | struct deh_mgr *deh = (void *)data; | |
39 | ||
40 | if (!deh) | |
41 | return; | |
42 | ||
43 | bridge_deh_notify(deh, DSP_MMUFAULT, 0); | |
44 | } | |
45 | ||
6c4c899e | 46 | static irqreturn_t mmu_fault_isr(int irq, void *data) |
f5bd96bb | 47 | { |
6c4c899e FC |
48 | struct deh_mgr *deh = data; |
49 | struct cfg_hostres *resources; | |
50 | u32 event; | |
f5bd96bb | 51 | |
6c4c899e FC |
52 | if (!deh) |
53 | return IRQ_HANDLED; | |
f5bd96bb | 54 | |
085467b8 | 55 | resources = deh->bridge_context->resources; |
6c4c899e FC |
56 | if (!resources) { |
57 | dev_dbg(bridge, "%s: Failed to get Host Resources\n", | |
58 | __func__); | |
59 | return IRQ_HANDLED; | |
60 | } | |
f5bd96bb | 61 | |
5108de0a | 62 | hw_mmu_event_status(resources->dmmu_base, &event); |
6c4c899e | 63 | if (event == HW_MMU_TRANSLATION_FAULT) { |
5108de0a | 64 | hw_mmu_fault_addr_read(resources->dmmu_base, &fault_addr); |
6c4c899e FC |
65 | dev_dbg(bridge, "%s: event=0x%x, fault_addr=0x%x\n", __func__, |
66 | event, fault_addr); | |
67 | /* | |
68 | * Schedule a DPC directly. In the future, it may be | |
69 | * necessary to check if DSP MMU fault is intended for | |
70 | * Bridge. | |
71 | */ | |
72 | tasklet_schedule(&deh->dpc_tasklet); | |
73 | ||
74 | /* Disable the MMU events, else once we clear it will | |
75 | * start to raise INTs again */ | |
5108de0a | 76 | hw_mmu_event_disable(resources->dmmu_base, |
6c4c899e FC |
77 | HW_MMU_TRANSLATION_FAULT); |
78 | } else { | |
5108de0a | 79 | hw_mmu_event_disable(resources->dmmu_base, |
6c4c899e FC |
80 | HW_MMU_ALL_INTERRUPTS); |
81 | } | |
82 | return IRQ_HANDLED; | |
f5bd96bb FC |
83 | } |
84 | ||
96904b06 | 85 | int bridge_deh_create(struct deh_mgr **ret_deh, |
999e07d6 ORL |
86 | struct dev_object *hdev_obj) |
87 | { | |
96904b06 FC |
88 | int status; |
89 | struct deh_mgr *deh; | |
999e07d6 ORL |
90 | struct bridge_dev_context *hbridge_context = NULL; |
91 | ||
92 | /* Message manager will be created when a file is loaded, since | |
93 | * size of message buffer in shared memory is configurable in | |
94 | * the base image. */ | |
95 | /* Get Bridge context info. */ | |
96 | dev_get_bridge_context(hdev_obj, &hbridge_context); | |
999e07d6 | 97 | /* Allocate IO manager object: */ |
96904b06 FC |
98 | deh = kzalloc(sizeof(*deh), GFP_KERNEL); |
99 | if (!deh) { | |
999e07d6 | 100 | status = -ENOMEM; |
0a466f69 | 101 | goto err; |
999e07d6 ORL |
102 | } |
103 | ||
104 | /* Create an NTFY object to manage notifications */ | |
96904b06 FC |
105 | deh->ntfy_obj = kmalloc(sizeof(struct ntfy_object), GFP_KERNEL); |
106 | if (!deh->ntfy_obj) { | |
999e07d6 ORL |
107 | status = -ENOMEM; |
108 | goto err; | |
109 | } | |
96904b06 | 110 | ntfy_init(deh->ntfy_obj); |
999e07d6 | 111 | |
f5bd96bb FC |
112 | /* Create a MMUfault DPC */ |
113 | tasklet_init(&deh->dpc_tasklet, mmu_fault_dpc, (u32) deh); | |
114 | ||
999e07d6 | 115 | /* Fill in context structure */ |
085467b8 | 116 | deh->bridge_context = hbridge_context; |
999e07d6 | 117 | |
50ad26f4 FC |
118 | /* Install ISR function for DSP MMU fault */ |
119 | status = request_irq(INT_DSP_MMU_IRQ, mmu_fault_isr, 0, | |
120 | "DspBridge\tiommu fault", deh); | |
121 | if (status < 0) | |
122 | goto err; | |
123 | ||
96904b06 | 124 | *ret_deh = deh; |
0a466f69 | 125 | return 0; |
999e07d6 | 126 | |
0a466f69 | 127 | err: |
96904b06 FC |
128 | bridge_deh_destroy(deh); |
129 | *ret_deh = NULL; | |
999e07d6 ORL |
130 | return status; |
131 | } | |
132 | ||
96904b06 | 133 | int bridge_deh_destroy(struct deh_mgr *deh) |
999e07d6 | 134 | { |
96904b06 | 135 | if (!deh) |
999e07d6 ORL |
136 | return -EFAULT; |
137 | ||
999e07d6 | 138 | /* If notification object exists, delete it */ |
96904b06 FC |
139 | if (deh->ntfy_obj) { |
140 | ntfy_delete(deh->ntfy_obj); | |
141 | kfree(deh->ntfy_obj); | |
999e07d6 | 142 | } |
50ad26f4 FC |
143 | /* Disable DSP MMU fault */ |
144 | free_irq(INT_DSP_MMU_IRQ, deh); | |
999e07d6 | 145 | |
f5bd96bb FC |
146 | /* Free DPC object */ |
147 | tasklet_kill(&deh->dpc_tasklet); | |
148 | ||
999e07d6 | 149 | /* Deallocate the DEH manager object */ |
96904b06 | 150 | kfree(deh); |
999e07d6 ORL |
151 | |
152 | return 0; | |
153 | } | |
154 | ||
96904b06 | 155 | int bridge_deh_register_notify(struct deh_mgr *deh, u32 event_mask, |
999e07d6 ORL |
156 | u32 notify_type, |
157 | struct dsp_notification *hnotification) | |
158 | { | |
96904b06 | 159 | if (!deh) |
999e07d6 ORL |
160 | return -EFAULT; |
161 | ||
162 | if (event_mask) | |
96904b06 | 163 | return ntfy_register(deh->ntfy_obj, hnotification, |
0a466f69 | 164 | event_mask, notify_type); |
999e07d6 | 165 | else |
96904b06 | 166 | return ntfy_unregister(deh->ntfy_obj, hnotification); |
999e07d6 ORL |
167 | } |
168 | ||
f5bd96bb FC |
169 | #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE |
170 | static void mmu_fault_print_stack(struct bridge_dev_context *dev_context) | |
171 | { | |
6c4c899e FC |
172 | struct cfg_hostres *resources; |
173 | struct hw_mmu_map_attrs_t map_attrs = { | |
174 | .endianism = HW_LITTLE_ENDIAN, | |
175 | .element_size = HW_ELEM_SIZE16BIT, | |
176 | .mixed_size = HW_MMU_CPUES, | |
177 | }; | |
178 | void *dummy_va_addr; | |
179 | ||
180 | resources = dev_context->resources; | |
181 | dummy_va_addr = (void*)__get_free_page(GFP_ATOMIC); | |
f5bd96bb FC |
182 | |
183 | /* | |
184 | * Before acking the MMU fault, let's make sure MMU can only | |
185 | * access entry #0. Then add a new entry so that the DSP OS | |
186 | * can continue in order to dump the stack. | |
187 | */ | |
5108de0a RS |
188 | hw_mmu_twl_disable(resources->dmmu_base); |
189 | hw_mmu_tlb_flush_all(resources->dmmu_base); | |
6c4c899e | 190 | |
5108de0a | 191 | hw_mmu_tlb_add(resources->dmmu_base, |
6c4c899e FC |
192 | virt_to_phys(dummy_va_addr), fault_addr, |
193 | HW_PAGE_SIZE4KB, 1, | |
194 | &map_attrs, HW_SET, HW_SET); | |
f5bd96bb FC |
195 | |
196 | dsp_clk_enable(DSP_CLK_GPT8); | |
197 | ||
198 | dsp_gpt_wait_overflow(DSP_CLK_GPT8, 0xfffffffe); | |
199 | ||
200 | /* Clear MMU interrupt */ | |
5108de0a | 201 | hw_mmu_event_ack(resources->dmmu_base, |
6c4c899e | 202 | HW_MMU_TRANSLATION_FAULT); |
f5bd96bb FC |
203 | dump_dsp_stack(dev_context); |
204 | dsp_clk_disable(DSP_CLK_GPT8); | |
205 | ||
5108de0a | 206 | hw_mmu_disable(resources->dmmu_base); |
6c4c899e | 207 | free_page((unsigned long)dummy_va_addr); |
f5bd96bb FC |
208 | } |
209 | #endif | |
210 | ||
96904b06 FC |
211 | static inline const char *event_to_string(int event) |
212 | { | |
213 | switch (event) { | |
214 | case DSP_SYSERROR: return "DSP_SYSERROR"; break; | |
215 | case DSP_MMUFAULT: return "DSP_MMUFAULT"; break; | |
216 | case DSP_PWRERROR: return "DSP_PWRERROR"; break; | |
217 | case DSP_WDTOVERFLOW: return "DSP_WDTOVERFLOW"; break; | |
73e29189 | 218 | default: return "unknown event"; break; |
96904b06 FC |
219 | } |
220 | } | |
221 | ||
222 | void bridge_deh_notify(struct deh_mgr *deh, int event, int info) | |
ec71c8fe FC |
223 | { |
224 | struct bridge_dev_context *dev_context; | |
96904b06 | 225 | const char *str = event_to_string(event); |
ec71c8fe | 226 | |
96904b06 | 227 | if (!deh) |
999e07d6 ORL |
228 | return; |
229 | ||
96904b06 | 230 | dev_dbg(bridge, "%s: device exception", __func__); |
085467b8 | 231 | dev_context = deh->bridge_context; |
999e07d6 | 232 | |
96904b06 | 233 | switch (event) { |
999e07d6 | 234 | case DSP_SYSERROR: |
96904b06 FC |
235 | dev_err(bridge, "%s: %s, info=0x%x", __func__, |
236 | str, info); | |
4f551c8f | 237 | #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE |
999e07d6 ORL |
238 | dump_dl_modules(dev_context); |
239 | dump_dsp_stack(dev_context); | |
4f551c8f | 240 | #endif |
999e07d6 ORL |
241 | break; |
242 | case DSP_MMUFAULT: | |
6c4c899e FC |
243 | dev_err(bridge, "%s: %s, addr=0x%x", __func__, |
244 | str, fault_addr); | |
f5bd96bb FC |
245 | #ifdef CONFIG_TIDSPBRIDGE_BACKTRACE |
246 | print_dsp_trace_buffer(dev_context); | |
247 | dump_dl_modules(dev_context); | |
248 | mmu_fault_print_stack(dev_context); | |
249 | #endif | |
999e07d6 ORL |
250 | break; |
251 | default: | |
96904b06 | 252 | dev_err(bridge, "%s: %s", __func__, str); |
999e07d6 ORL |
253 | break; |
254 | } | |
255 | ||
256 | /* Filter subsequent notifications when an error occurs */ | |
b4da7fc3 | 257 | if (dev_context->brd_state != BRD_ERROR) { |
96904b06 | 258 | ntfy_notify(deh->ntfy_obj, event); |
b3d23688 | 259 | #ifdef CONFIG_TIDSPBRIDGE_RECOVERY |
999e07d6 ORL |
260 | bridge_recover_schedule(); |
261 | #endif | |
262 | } | |
263 | ||
264 | /* Set the Board state as ERROR */ | |
b4da7fc3 | 265 | dev_context->brd_state = BRD_ERROR; |
999e07d6 ORL |
266 | /* Disable all the clocks that were enabled by DSP */ |
267 | dsp_clock_disable_all(dev_context->dsp_per_clks); | |
268 | /* | |
269 | * Avoid the subsequent WDT if it happens once, | |
270 | * also if fatal error occurs. | |
271 | */ | |
272 | dsp_wdt_enable(false); | |
273 | } |