aboutsummaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>2011-12-05 03:39:59 -0500
committerRussell King <rmk+kernel@arm.linux.org.uk>2011-12-06 06:16:13 -0500
commitde66a979012dbc66b1ec0125795a3f79ee667b8a (patch)
tree078ddb11791244de56fb6271a6be11b5ce622907 /arch
parent974c07249b06d948154be3275bf4f6e55b585300 (diff)
ARM: 7187/1: fix unwinding for XIP kernels
The linker places the unwind tables in readonly sections. So when using an XIP kernel these are located in ROM and cannot be modified. For that reason the current approach to convert the relative offsets in the unwind index to absolute addresses early in the boot process doesn't work with XIP. The offsets in the unwind index section are signed 31 bit numbers and the structs are sorted by this offset. So it first has offsets between 0x40000000 and 0x7fffffff (i.e. the negative offsets) and then offsets between 0x00000000 and 0x3fffffff. When seperating these two blocks the numbers are sorted even when interpreting the offsets as unsigned longs. So determine the first non-negative entry once and track that using the new origin pointer. The actual bisection can then use a plain unsigned long comparison. The only thing that makes the new bisection more complicated is that the offsets are relative to their position in the index section, so the key to search needs to be adapted accordingly in each step. Moreover several consts are added to catch future writes and rename the member "addr" of struct unwind_idx to "addr_offset" to better match the new semantic. (This has the additional benefit of breaking eventual users at compile time to make them aware of the change.) In my tests the new algorithm was a tad faster than the original and has the additional upside of not needing the initial conversion and so saves some boot time and it's possible to unwind even earlier. Acked-by: Catalin Marinas <catalin.marinas@arm.com> Acked-by: Nicolas Pitre <nico@fluxnic.net> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch')
-rw-r--r--arch/arm/include/asm/unwind.h16
-rw-r--r--arch/arm/kernel/setup.c2
-rw-r--r--arch/arm/kernel/unwind.c129
3 files changed, 88 insertions, 59 deletions
diff --git a/arch/arm/include/asm/unwind.h b/arch/arm/include/asm/unwind.h
index a5edf421005..d1c3f3a71c9 100644
--- a/arch/arm/include/asm/unwind.h
+++ b/arch/arm/include/asm/unwind.h
@@ -30,14 +30,15 @@ enum unwind_reason_code {
30}; 30};
31 31
32struct unwind_idx { 32struct unwind_idx {
33 unsigned long addr; 33 unsigned long addr_offset;
34 unsigned long insn; 34 unsigned long insn;
35}; 35};
36 36
37struct unwind_table { 37struct unwind_table {
38 struct list_head list; 38 struct list_head list;
39 struct unwind_idx *start; 39 const struct unwind_idx *start;
40 struct unwind_idx *stop; 40 const struct unwind_idx *origin;
41 const struct unwind_idx *stop;
41 unsigned long begin_addr; 42 unsigned long begin_addr;
42 unsigned long end_addr; 43 unsigned long end_addr;
43}; 44};
@@ -49,15 +50,6 @@ extern struct unwind_table *unwind_table_add(unsigned long start,
49extern void unwind_table_del(struct unwind_table *tab); 50extern void unwind_table_del(struct unwind_table *tab);
50extern void unwind_backtrace(struct pt_regs *regs, struct task_struct *tsk); 51extern void unwind_backtrace(struct pt_regs *regs, struct task_struct *tsk);
51 52
52#ifdef CONFIG_ARM_UNWIND
53extern int __init unwind_init(void);
54#else
55static inline int __init unwind_init(void)
56{
57 return 0;
58}
59#endif
60
61#endif /* !__ASSEMBLY__ */ 53#endif /* !__ASSEMBLY__ */
62 54
63#ifdef CONFIG_ARM_UNWIND 55#ifdef CONFIG_ARM_UNWIND
diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c
index 3448a3f9cc8..1bdd0adbb7d 100644
--- a/arch/arm/kernel/setup.c
+++ b/arch/arm/kernel/setup.c
@@ -895,8 +895,6 @@ void __init setup_arch(char **cmdline_p)
895{ 895{
896 struct machine_desc *mdesc; 896 struct machine_desc *mdesc;
897 897
898 unwind_init();
899
900 setup_processor(); 898 setup_processor();
901 mdesc = setup_machine_fdt(__atags_pointer); 899 mdesc = setup_machine_fdt(__atags_pointer);
902 if (!mdesc) 900 if (!mdesc)
diff --git a/arch/arm/kernel/unwind.c b/arch/arm/kernel/unwind.c
index e7e8365795c..3f03fe0c326 100644
--- a/arch/arm/kernel/unwind.c
+++ b/arch/arm/kernel/unwind.c
@@ -67,7 +67,7 @@ EXPORT_SYMBOL(__aeabi_unwind_cpp_pr2);
67 67
68struct unwind_ctrl_block { 68struct unwind_ctrl_block {
69 unsigned long vrs[16]; /* virtual register set */ 69 unsigned long vrs[16]; /* virtual register set */
70 unsigned long *insn; /* pointer to the current instructions word */ 70 const unsigned long *insn; /* pointer to the current instructions word */
71 int entries; /* number of entries left to interpret */ 71 int entries; /* number of entries left to interpret */
72 int byte; /* current byte number in the instructions word */ 72 int byte; /* current byte number in the instructions word */
73}; 73};
@@ -83,8 +83,9 @@ enum regs {
83 PC = 15 83 PC = 15
84}; 84};
85 85
86extern struct unwind_idx __start_unwind_idx[]; 86extern const struct unwind_idx __start_unwind_idx[];
87extern struct unwind_idx __stop_unwind_idx[]; 87static const struct unwind_idx *__origin_unwind_idx;
88extern const struct unwind_idx __stop_unwind_idx[];
88 89
89static DEFINE_SPINLOCK(unwind_lock); 90static DEFINE_SPINLOCK(unwind_lock);
90static LIST_HEAD(unwind_tables); 91static LIST_HEAD(unwind_tables);
@@ -98,45 +99,99 @@ static LIST_HEAD(unwind_tables);
98}) 99})
99 100
100/* 101/*
101 * Binary search in the unwind index. The entries entries are 102 * Binary search in the unwind index. The entries are
102 * guaranteed to be sorted in ascending order by the linker. 103 * guaranteed to be sorted in ascending order by the linker.
104 *
105 * start = first entry
106 * origin = first entry with positive offset (or stop if there is no such entry)
107 * stop - 1 = last entry
103 */ 108 */
104static struct unwind_idx *search_index(unsigned long addr, 109static const struct unwind_idx *search_index(unsigned long addr,
105 struct unwind_idx *first, 110 const struct unwind_idx *start,
106 struct unwind_idx *last) 111 const struct unwind_idx *origin,
112 const struct unwind_idx *stop)
107{ 113{
108 pr_debug("%s(%08lx, %p, %p)\n", __func__, addr, first, last); 114 unsigned long addr_prel31;
115
116 pr_debug("%s(%08lx, %p, %p, %p)\n",
117 __func__, addr, start, origin, stop);
118
119 /*
120 * only search in the section with the matching sign. This way the
121 * prel31 numbers can be compared as unsigned longs.
122 */
123 if (addr < (unsigned long)start)
124 /* negative offsets: [start; origin) */
125 stop = origin;
126 else
127 /* positive offsets: [origin; stop) */
128 start = origin;
129
130 /* prel31 for address relavive to start */
131 addr_prel31 = (addr - (unsigned long)start) & 0x7fffffff;
109 132
110 if (addr < first->addr) { 133 while (start < stop - 1) {
134 const struct unwind_idx *mid = start + ((stop - start) >> 1);
135
136 /*
137 * As addr_prel31 is relative to start an offset is needed to
138 * make it relative to mid.
139 */
140 if (addr_prel31 - ((unsigned long)mid - (unsigned long)start) <
141 mid->addr_offset)
142 stop = mid;
143 else {
144 /* keep addr_prel31 relative to start */
145 addr_prel31 -= ((unsigned long)mid -
146 (unsigned long)start);
147 start = mid;
148 }
149 }
150
151 if (likely(start->addr_offset <= addr_prel31))
152 return start;
153 else {
111 pr_warning("unwind: Unknown symbol address %08lx\n", addr); 154 pr_warning("unwind: Unknown symbol address %08lx\n", addr);
112 return NULL; 155 return NULL;
113 } else if (addr >= last->addr) 156 }
114 return last; 157}
115 158
116 while (first < last - 1) { 159static const struct unwind_idx *unwind_find_origin(
117 struct unwind_idx *mid = first + ((last - first + 1) >> 1); 160 const struct unwind_idx *start, const struct unwind_idx *stop)
161{
162 pr_debug("%s(%p, %p)\n", __func__, start, stop);
163 while (start < stop - 1) {
164 const struct unwind_idx *mid = start + ((stop - start) >> 1);
118 165
119 if (addr < mid->addr) 166 if (mid->addr_offset >= 0x40000000)
120 last = mid; 167 /* negative offset */
168 start = mid;
121 else 169 else
122 first = mid; 170 /* positive offset */
171 stop = mid;
123 } 172 }
124 173 pr_debug("%s -> %p\n", __func__, stop);
125 return first; 174 return stop;
126} 175}
127 176
128static struct unwind_idx *unwind_find_idx(unsigned long addr) 177static const struct unwind_idx *unwind_find_idx(unsigned long addr)
129{ 178{
130 struct unwind_idx *idx = NULL; 179 const struct unwind_idx *idx = NULL;
131 unsigned long flags; 180 unsigned long flags;
132 181
133 pr_debug("%s(%08lx)\n", __func__, addr); 182 pr_debug("%s(%08lx)\n", __func__, addr);
134 183
135 if (core_kernel_text(addr)) 184 if (core_kernel_text(addr)) {
185 if (unlikely(!__origin_unwind_idx))
186 __origin_unwind_idx =
187 unwind_find_origin(__start_unwind_idx,
188 __stop_unwind_idx);
189
136 /* main unwind table */ 190 /* main unwind table */
137 idx = search_index(addr, __start_unwind_idx, 191 idx = search_index(addr, __start_unwind_idx,
138 __stop_unwind_idx - 1); 192 __origin_unwind_idx,
139 else { 193 __stop_unwind_idx);
194 } else {
140 /* module unwind tables */ 195 /* module unwind tables */
141 struct unwind_table *table; 196 struct unwind_table *table;
142 197
@@ -145,7 +200,8 @@ static struct unwind_idx *unwind_find_idx(unsigned long addr)
145 if (addr >= table->begin_addr && 200 if (addr >= table->begin_addr &&
146 addr < table->end_addr) { 201 addr < table->end_addr) {
147 idx = search_index(addr, table->start, 202 idx = search_index(addr, table->start,
148 table->stop - 1); 203 table->origin,
204 table->stop);
149 /* Move-to-front to exploit common traces */ 205 /* Move-to-front to exploit common traces */
150 list_move(&table->list, &unwind_tables); 206 list_move(&table->list, &unwind_tables);
151 break; 207 break;
@@ -274,7 +330,7 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)
274int unwind_frame(struct stackframe *frame) 330int unwind_frame(struct stackframe *frame)
275{ 331{
276 unsigned long high, low; 332 unsigned long high, low;
277 struct unwind_idx *idx; 333 const struct unwind_idx *idx;
278 struct unwind_ctrl_block ctrl; 334 struct unwind_ctrl_block ctrl;
279 335
280 /* only go to a higher address on the stack */ 336 /* only go to a higher address on the stack */
@@ -399,7 +455,6 @@ struct unwind_table *unwind_table_add(unsigned long start, unsigned long size,
399 unsigned long text_size) 455 unsigned long text_size)
400{ 456{
401 unsigned long flags; 457 unsigned long flags;
402 struct unwind_idx *idx;
403 struct unwind_table *tab = kmalloc(sizeof(*tab), GFP_KERNEL); 458 struct unwind_table *tab = kmalloc(sizeof(*tab), GFP_KERNEL);
404 459
405 pr_debug("%s(%08lx, %08lx, %08lx, %08lx)\n", __func__, start, size, 460 pr_debug("%s(%08lx, %08lx, %08lx, %08lx)\n", __func__, start, size,
@@ -408,15 +463,12 @@ struct unwind_table *unwind_table_add(unsigned long start, unsigned long size,
408 if (!tab) 463 if (!tab)
409 return tab; 464 return tab;
410 465
411 tab->start = (struct unwind_idx *)start; 466 tab->start = (const struct unwind_idx *)start;
412 tab->stop = (struct unwind_idx *)(start + size); 467 tab->stop = (const struct unwind_idx *)(start + size);
468 tab->origin = unwind_find_origin(tab->start, tab->stop);
413 tab->begin_addr = text_addr; 469 tab->begin_addr = text_addr;
414 tab->end_addr = text_addr + text_size; 470 tab->end_addr = text_addr + text_size;
415 471
416 /* Convert the symbol addresses to absolute values */
417 for (idx = tab->start; idx < tab->stop; idx++)
418 idx->addr = prel31_to_addr(&idx->addr);
419
420 spin_lock_irqsave(&unwind_lock, flags); 472 spin_lock_irqsave(&unwind_lock, flags);
421 list_add_tail(&tab->list, &unwind_tables); 473 list_add_tail(&tab->list, &unwind_tables);
422 spin_unlock_irqrestore(&unwind_lock, flags); 474 spin_unlock_irqrestore(&unwind_lock, flags);
@@ -437,16 +489,3 @@ void unwind_table_del(struct unwind_table *tab)
437 489
438 kfree(tab); 490 kfree(tab);
439} 491}
440
441int __init unwind_init(void)
442{
443 struct unwind_idx *idx;
444
445 /* Convert the symbol addresses to absolute values */
446 for (idx = __start_unwind_idx; idx < __stop_unwind_idx; idx++)
447 idx->addr = prel31_to_addr(&idx->addr);
448
449 pr_debug("unwind: ARM stack unwinding initialised\n");
450
451 return 0;
452}