aboutsummaryrefslogtreecommitdiffstats
path: root/arch/microblaze/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'arch/microblaze/kernel')
-rw-r--r--arch/microblaze/kernel/asm-offsets.c1
-rw-r--r--arch/microblaze/kernel/cpu/cache.c75
-rw-r--r--arch/microblaze/kernel/cpu/mb.c10
-rw-r--r--arch/microblaze/kernel/dma.c2
-rw-r--r--arch/microblaze/kernel/entry-nommu.S2
-rw-r--r--arch/microblaze/kernel/exceptions.c2
-rw-r--r--arch/microblaze/kernel/head.S3
-rw-r--r--arch/microblaze/kernel/irq.c3
-rw-r--r--arch/microblaze/kernel/microblaze_ksyms.c11
-rw-r--r--arch/microblaze/kernel/misc.S36
-rw-r--r--arch/microblaze/kernel/module.c2
-rw-r--r--arch/microblaze/kernel/traps.c34
-rw-r--r--arch/microblaze/kernel/vmlinux.lds.S3
13 files changed, 72 insertions, 112 deletions
diff --git a/arch/microblaze/kernel/asm-offsets.c b/arch/microblaze/kernel/asm-offsets.c
index 0071260a672c..c1b459c97571 100644
--- a/arch/microblaze/kernel/asm-offsets.c
+++ b/arch/microblaze/kernel/asm-offsets.c
@@ -16,6 +16,7 @@
16#include <linux/hardirq.h> 16#include <linux/hardirq.h>
17#include <linux/thread_info.h> 17#include <linux/thread_info.h>
18#include <linux/kbuild.h> 18#include <linux/kbuild.h>
19#include <asm/cpuinfo.h>
19 20
20int main(int argc, char *argv[]) 21int main(int argc, char *argv[])
21{ 22{
diff --git a/arch/microblaze/kernel/cpu/cache.c b/arch/microblaze/kernel/cpu/cache.c
index f04d8a86dead..109876e8d643 100644
--- a/arch/microblaze/kernel/cpu/cache.c
+++ b/arch/microblaze/kernel/cpu/cache.c
@@ -96,13 +96,16 @@ static inline void __disable_dcache_nomsr(void)
96} 96}
97 97
98 98
99/* Helper macro for computing the limits of cache range loops */ 99/* Helper macro for computing the limits of cache range loops
100 *
101 * End address can be unaligned which is OK for C implementation.
102 * ASM implementation align it in ASM macros
103 */
100#define CACHE_LOOP_LIMITS(start, end, cache_line_length, cache_size) \ 104#define CACHE_LOOP_LIMITS(start, end, cache_line_length, cache_size) \
101do { \ 105do { \
102 int align = ~(cache_line_length - 1); \ 106 int align = ~(cache_line_length - 1); \
103 end = min(start + cache_size, end); \ 107 end = min(start + cache_size, end); \
104 start &= align; \ 108 start &= align; \
105 end = ((end & align) + cache_line_length); \
106} while (0); 109} while (0);
107 110
108/* 111/*
@@ -111,9 +114,9 @@ do { \
111 */ 114 */
112#define CACHE_ALL_LOOP(cache_size, line_length, op) \ 115#define CACHE_ALL_LOOP(cache_size, line_length, op) \
113do { \ 116do { \
114 unsigned int len = cache_size; \ 117 unsigned int len = cache_size - line_length; \
115 int step = -line_length; \ 118 int step = -line_length; \
116 BUG_ON(step >= 0); \ 119 WARN_ON(step >= 0); \
117 \ 120 \
118 __asm__ __volatile__ (" 1: " #op " %0, r0; \ 121 __asm__ __volatile__ (" 1: " #op " %0, r0; \
119 bgtid %0, 1b; \ 122 bgtid %0, 1b; \
@@ -122,26 +125,22 @@ do { \
122 : "memory"); \ 125 : "memory"); \
123} while (0); 126} while (0);
124 127
125 128/* Used for wdc.flush/clear which can use rB for offset which is not possible
126#define CACHE_ALL_LOOP2(cache_size, line_length, op) \ 129 * to use for simple wdc or wic.
127do { \ 130 *
128 unsigned int len = cache_size; \ 131 * start address is cache aligned
129 int step = -line_length; \ 132 * end address is not aligned, if end is aligned then I have to substract
130 BUG_ON(step >= 0); \ 133 * cacheline length because I can't flush/invalidate the next cacheline.
131 \ 134 * If is not, I align it because I will flush/invalidate whole line.
132 __asm__ __volatile__ (" 1: " #op " r0, %0; \ 135 */
133 bgtid %0, 1b; \
134 addk %0, %0, %1; \
135 " : : "r" (len), "r" (step) \
136 : "memory"); \
137} while (0);
138
139/* for wdc.flush/clear */
140#define CACHE_RANGE_LOOP_2(start, end, line_length, op) \ 136#define CACHE_RANGE_LOOP_2(start, end, line_length, op) \
141do { \ 137do { \
142 int step = -line_length; \ 138 int step = -line_length; \
143 int count = end - start; \ 139 int align = ~(line_length - 1); \
144 BUG_ON(count <= 0); \ 140 int count; \
141 end = ((end & align) == end) ? end - line_length : end & align; \
142 count = end - start; \
143 WARN_ON(count < 0); \
145 \ 144 \
146 __asm__ __volatile__ (" 1: " #op " %0, %1; \ 145 __asm__ __volatile__ (" 1: " #op " %0, %1; \
147 bgtid %1, 1b; \ 146 bgtid %1, 1b; \
@@ -154,7 +153,9 @@ do { \
154#define CACHE_RANGE_LOOP_1(start, end, line_length, op) \ 153#define CACHE_RANGE_LOOP_1(start, end, line_length, op) \
155do { \ 154do { \
156 int volatile temp; \ 155 int volatile temp; \
157 BUG_ON(end - start <= 0); \ 156 int align = ~(line_length - 1); \
157 end = ((end & align) == end) ? end - line_length : end & align; \
158 WARN_ON(end - start < 0); \
158 \ 159 \
159 __asm__ __volatile__ (" 1: " #op " %1, r0; \ 160 __asm__ __volatile__ (" 1: " #op " %1, r0; \
160 cmpu %0, %1, %2; \ 161 cmpu %0, %1, %2; \
@@ -360,8 +361,12 @@ static void __invalidate_dcache_all_noirq_wt(void)
360#endif 361#endif
361} 362}
362 363
363/* FIXME this is weird - should be only wdc but not work 364/* FIXME It is blindly invalidation as is expected
364 * MS: I am getting bus errors and other weird things */ 365 * but can't be called on noMMU in microblaze_cache_init below
366 *
367 * MS: noMMU kernel won't boot if simple wdc is used
368 * The reason should be that there are discared data which kernel needs
369 */
365static void __invalidate_dcache_all_wb(void) 370static void __invalidate_dcache_all_wb(void)
366{ 371{
367#ifndef ASM_LOOP 372#ifndef ASM_LOOP
@@ -369,12 +374,12 @@ static void __invalidate_dcache_all_wb(void)
369#endif 374#endif
370 pr_debug("%s\n", __func__); 375 pr_debug("%s\n", __func__);
371#ifdef ASM_LOOP 376#ifdef ASM_LOOP
372 CACHE_ALL_LOOP2(cpuinfo.dcache_size, cpuinfo.dcache_line_length, 377 CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length,
373 wdc.clear) 378 wdc)
374#else 379#else
375 for (i = 0; i < cpuinfo.dcache_size; 380 for (i = 0; i < cpuinfo.dcache_size;
376 i += cpuinfo.dcache_line_length) 381 i += cpuinfo.dcache_line_length)
377 __asm__ __volatile__ ("wdc.clear %0, r0;" \ 382 __asm__ __volatile__ ("wdc %0, r0;" \
378 : : "r" (i)); 383 : : "r" (i));
379#endif 384#endif
380} 385}
@@ -393,7 +398,7 @@ static void __invalidate_dcache_range_wb(unsigned long start,
393#ifdef ASM_LOOP 398#ifdef ASM_LOOP
394 CACHE_RANGE_LOOP_2(start, end, cpuinfo.dcache_line_length, wdc.clear); 399 CACHE_RANGE_LOOP_2(start, end, cpuinfo.dcache_line_length, wdc.clear);
395#else 400#else
396 for (i = start; i < end; i += cpuinfo.icache_line_length) 401 for (i = start; i < end; i += cpuinfo.dcache_line_length)
397 __asm__ __volatile__ ("wdc.clear %0, r0;" \ 402 __asm__ __volatile__ ("wdc.clear %0, r0;" \
398 : : "r" (i)); 403 : : "r" (i));
399#endif 404#endif
@@ -413,7 +418,7 @@ static void __invalidate_dcache_range_nomsr_wt(unsigned long start,
413#ifdef ASM_LOOP 418#ifdef ASM_LOOP
414 CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc); 419 CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc);
415#else 420#else
416 for (i = start; i < end; i += cpuinfo.icache_line_length) 421 for (i = start; i < end; i += cpuinfo.dcache_line_length)
417 __asm__ __volatile__ ("wdc %0, r0;" \ 422 __asm__ __volatile__ ("wdc %0, r0;" \
418 : : "r" (i)); 423 : : "r" (i));
419#endif 424#endif
@@ -437,7 +442,7 @@ static void __invalidate_dcache_range_msr_irq_wt(unsigned long start,
437#ifdef ASM_LOOP 442#ifdef ASM_LOOP
438 CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc); 443 CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc);
439#else 444#else
440 for (i = start; i < end; i += cpuinfo.icache_line_length) 445 for (i = start; i < end; i += cpuinfo.dcache_line_length)
441 __asm__ __volatile__ ("wdc %0, r0;" \ 446 __asm__ __volatile__ ("wdc %0, r0;" \
442 : : "r" (i)); 447 : : "r" (i));
443#endif 448#endif
@@ -465,7 +470,7 @@ static void __invalidate_dcache_range_nomsr_irq(unsigned long start,
465#ifdef ASM_LOOP 470#ifdef ASM_LOOP
466 CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc); 471 CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc);
467#else 472#else
468 for (i = start; i < end; i += cpuinfo.icache_line_length) 473 for (i = start; i < end; i += cpuinfo.dcache_line_length)
469 __asm__ __volatile__ ("wdc %0, r0;" \ 474 __asm__ __volatile__ ("wdc %0, r0;" \
470 : : "r" (i)); 475 : : "r" (i));
471#endif 476#endif
@@ -504,7 +509,7 @@ static void __flush_dcache_range_wb(unsigned long start, unsigned long end)
504#ifdef ASM_LOOP 509#ifdef ASM_LOOP
505 CACHE_RANGE_LOOP_2(start, end, cpuinfo.dcache_line_length, wdc.flush); 510 CACHE_RANGE_LOOP_2(start, end, cpuinfo.dcache_line_length, wdc.flush);
506#else 511#else
507 for (i = start; i < end; i += cpuinfo.icache_line_length) 512 for (i = start; i < end; i += cpuinfo.dcache_line_length)
508 __asm__ __volatile__ ("wdc.flush %0, r0;" \ 513 __asm__ __volatile__ ("wdc.flush %0, r0;" \
509 : : "r" (i)); 514 : : "r" (i));
510#endif 515#endif
@@ -650,7 +655,11 @@ void microblaze_cache_init(void)
650 } 655 }
651 } 656 }
652 } 657 }
653 invalidate_dcache(); 658/* FIXME Invalidation is done in U-BOOT
659 * WT cache: Data is already written to main memory
660 * WB cache: Discard data on noMMU which caused that kernel doesn't boot
661 */
662 /* invalidate_dcache(); */
654 enable_dcache(); 663 enable_dcache();
655 664
656 invalidate_icache(); 665 invalidate_icache();
diff --git a/arch/microblaze/kernel/cpu/mb.c b/arch/microblaze/kernel/cpu/mb.c
index 0c912b2a8e03..4216eb1eaa32 100644
--- a/arch/microblaze/kernel/cpu/mb.c
+++ b/arch/microblaze/kernel/cpu/mb.c
@@ -98,15 +98,17 @@ static int show_cpuinfo(struct seq_file *m, void *v)
98 98
99 if (cpuinfo.use_icache) 99 if (cpuinfo.use_icache)
100 count += seq_printf(m, 100 count += seq_printf(m,
101 "Icache:\t\t%ukB\n", 101 "Icache:\t\t%ukB\tline length:\t%dB\n",
102 cpuinfo.icache_size >> 10); 102 cpuinfo.icache_size >> 10,
103 cpuinfo.icache_line_length);
103 else 104 else
104 count += seq_printf(m, "Icache:\t\tno\n"); 105 count += seq_printf(m, "Icache:\t\tno\n");
105 106
106 if (cpuinfo.use_dcache) { 107 if (cpuinfo.use_dcache) {
107 count += seq_printf(m, 108 count += seq_printf(m,
108 "Dcache:\t\t%ukB\n", 109 "Dcache:\t\t%ukB\tline length:\t%dB\n",
109 cpuinfo.dcache_size >> 10); 110 cpuinfo.dcache_size >> 10,
111 cpuinfo.dcache_line_length);
110 if (cpuinfo.dcache_wb) 112 if (cpuinfo.dcache_wb)
111 count += seq_printf(m, "\t\twrite-back\n"); 113 count += seq_printf(m, "\t\twrite-back\n");
112 else 114 else
diff --git a/arch/microblaze/kernel/dma.c b/arch/microblaze/kernel/dma.c
index ce72dd4967cf..9dcd90b5df55 100644
--- a/arch/microblaze/kernel/dma.c
+++ b/arch/microblaze/kernel/dma.c
@@ -74,7 +74,7 @@ static void dma_direct_free_coherent(struct device *dev, size_t size,
74 void *vaddr, dma_addr_t dma_handle) 74 void *vaddr, dma_addr_t dma_handle)
75{ 75{
76#ifdef NOT_COHERENT_CACHE 76#ifdef NOT_COHERENT_CACHE
77 consistent_free(vaddr); 77 consistent_free(size, vaddr);
78#else 78#else
79 free_pages((unsigned long)vaddr, get_order(size)); 79 free_pages((unsigned long)vaddr, get_order(size));
80#endif 80#endif
diff --git a/arch/microblaze/kernel/entry-nommu.S b/arch/microblaze/kernel/entry-nommu.S
index 391d6197fc3b..8cc18cd2cce6 100644
--- a/arch/microblaze/kernel/entry-nommu.S
+++ b/arch/microblaze/kernel/entry-nommu.S
@@ -476,6 +476,8 @@ ENTRY(ret_from_fork)
476 nop 476 nop
477 477
478work_pending: 478work_pending:
479 enable_irq
480
479 andi r11, r19, _TIF_NEED_RESCHED 481 andi r11, r19, _TIF_NEED_RESCHED
480 beqi r11, 1f 482 beqi r11, 1f
481 bralid r15, schedule 483 bralid r15, schedule
diff --git a/arch/microblaze/kernel/exceptions.c b/arch/microblaze/kernel/exceptions.c
index d9f70f83097f..02cbdfe5aa8d 100644
--- a/arch/microblaze/kernel/exceptions.c
+++ b/arch/microblaze/kernel/exceptions.c
@@ -121,7 +121,7 @@ asmlinkage void full_exception(struct pt_regs *regs, unsigned int type,
121 } 121 }
122 printk(KERN_WARNING "Divide by zero exception " \ 122 printk(KERN_WARNING "Divide by zero exception " \
123 "in kernel mode.\n"); 123 "in kernel mode.\n");
124 die("Divide by exception", regs, SIGBUS); 124 die("Divide by zero exception", regs, SIGBUS);
125 break; 125 break;
126 case MICROBLAZE_FPU_EXCEPTION: 126 case MICROBLAZE_FPU_EXCEPTION:
127 pr_debug(KERN_WARNING "FPU exception\n"); 127 pr_debug(KERN_WARNING "FPU exception\n");
diff --git a/arch/microblaze/kernel/head.S b/arch/microblaze/kernel/head.S
index da6a5f5dc766..1bf739888260 100644
--- a/arch/microblaze/kernel/head.S
+++ b/arch/microblaze/kernel/head.S
@@ -28,6 +28,7 @@
28 * for more details. 28 * for more details.
29 */ 29 */
30 30
31#include <linux/init.h>
31#include <linux/linkage.h> 32#include <linux/linkage.h>
32#include <asm/thread_info.h> 33#include <asm/thread_info.h>
33#include <asm/page.h> 34#include <asm/page.h>
@@ -49,7 +50,7 @@ swapper_pg_dir:
49 50
50#endif /* CONFIG_MMU */ 51#endif /* CONFIG_MMU */
51 52
52 .text 53 __HEAD
53ENTRY(_start) 54ENTRY(_start)
54#if CONFIG_KERNEL_BASE_ADDR == 0 55#if CONFIG_KERNEL_BASE_ADDR == 0
55 brai TOPHYS(real_start) 56 brai TOPHYS(real_start)
diff --git a/arch/microblaze/kernel/irq.c b/arch/microblaze/kernel/irq.c
index 6f39e2c001f3..8f120aca123d 100644
--- a/arch/microblaze/kernel/irq.c
+++ b/arch/microblaze/kernel/irq.c
@@ -9,6 +9,7 @@
9 */ 9 */
10 10
11#include <linux/init.h> 11#include <linux/init.h>
12#include <linux/ftrace.h>
12#include <linux/kernel.h> 13#include <linux/kernel.h>
13#include <linux/hardirq.h> 14#include <linux/hardirq.h>
14#include <linux/interrupt.h> 15#include <linux/interrupt.h>
@@ -32,7 +33,7 @@ EXPORT_SYMBOL_GPL(irq_of_parse_and_map);
32 33
33static u32 concurrent_irq; 34static u32 concurrent_irq;
34 35
35void do_IRQ(struct pt_regs *regs) 36void __irq_entry do_IRQ(struct pt_regs *regs)
36{ 37{
37 unsigned int irq; 38 unsigned int irq;
38 struct pt_regs *old_regs = set_irq_regs(regs); 39 struct pt_regs *old_regs = set_irq_regs(regs);
diff --git a/arch/microblaze/kernel/microblaze_ksyms.c b/arch/microblaze/kernel/microblaze_ksyms.c
index bc4dcb7d3861..ff85f7718035 100644
--- a/arch/microblaze/kernel/microblaze_ksyms.c
+++ b/arch/microblaze/kernel/microblaze_ksyms.c
@@ -52,3 +52,14 @@ EXPORT_SYMBOL_GPL(_ebss);
52extern void _mcount(void); 52extern void _mcount(void);
53EXPORT_SYMBOL(_mcount); 53EXPORT_SYMBOL(_mcount);
54#endif 54#endif
55
56/*
57 * Assembly functions that may be used (directly or indirectly) by modules
58 */
59EXPORT_SYMBOL(__copy_tofrom_user);
60EXPORT_SYMBOL(__strncpy_user);
61
62#ifdef CONFIG_OPT_LIB_ASM
63EXPORT_SYMBOL(memcpy);
64EXPORT_SYMBOL(memmove);
65#endif
diff --git a/arch/microblaze/kernel/misc.S b/arch/microblaze/kernel/misc.S
index 7cf86498326c..0fb5fc6c1fc2 100644
--- a/arch/microblaze/kernel/misc.S
+++ b/arch/microblaze/kernel/misc.S
@@ -93,39 +93,3 @@ early_console_reg_tlb_alloc:
93 nop 93 nop
94 94
95 .size early_console_reg_tlb_alloc, . - early_console_reg_tlb_alloc 95 .size early_console_reg_tlb_alloc, . - early_console_reg_tlb_alloc
96
97/*
98 * Copy a whole page (4096 bytes).
99 */
100#define COPY_16_BYTES \
101 lwi r7, r6, 0; \
102 lwi r8, r6, 4; \
103 lwi r9, r6, 8; \
104 lwi r10, r6, 12; \
105 swi r7, r5, 0; \
106 swi r8, r5, 4; \
107 swi r9, r5, 8; \
108 swi r10, r5, 12
109
110
111/* FIXME DCACHE_LINE_BYTES (CONFIG_XILINX_MICROBLAZE0_DCACHE_LINE_LEN * 4)*/
112#define DCACHE_LINE_BYTES (4 * 4)
113
114.globl copy_page;
115.type copy_page, @function
116.align 4;
117copy_page:
118 ori r11, r0, (PAGE_SIZE/DCACHE_LINE_BYTES) - 1
119_copy_page_loop:
120 COPY_16_BYTES
121#if DCACHE_LINE_BYTES >= 32
122 COPY_16_BYTES
123#endif
124 addik r6, r6, DCACHE_LINE_BYTES
125 addik r5, r5, DCACHE_LINE_BYTES
126 bneid r11, _copy_page_loop
127 addik r11, r11, -1
128 rtsd r15, 8
129 nop
130
131 .size copy_page, . - copy_page
diff --git a/arch/microblaze/kernel/module.c b/arch/microblaze/kernel/module.c
index cbecf110dc30..0e73f6606547 100644
--- a/arch/microblaze/kernel/module.c
+++ b/arch/microblaze/kernel/module.c
@@ -16,6 +16,7 @@
16#include <linux/string.h> 16#include <linux/string.h>
17 17
18#include <asm/pgtable.h> 18#include <asm/pgtable.h>
19#include <asm/cacheflush.h>
19 20
20void *module_alloc(unsigned long size) 21void *module_alloc(unsigned long size)
21{ 22{
@@ -151,6 +152,7 @@ int apply_relocate_add(Elf32_Shdr *sechdrs, const char *strtab,
151int module_finalize(const Elf32_Ehdr *hdr, const Elf_Shdr *sechdrs, 152int module_finalize(const Elf32_Ehdr *hdr, const Elf_Shdr *sechdrs,
152 struct module *module) 153 struct module *module)
153{ 154{
155 flush_dcache();
154 return 0; 156 return 0;
155} 157}
156 158
diff --git a/arch/microblaze/kernel/traps.c b/arch/microblaze/kernel/traps.c
index 5e4570ef515c..75e49202a5ed 100644
--- a/arch/microblaze/kernel/traps.c
+++ b/arch/microblaze/kernel/traps.c
@@ -95,37 +95,3 @@ void dump_stack(void)
95 show_stack(NULL, NULL); 95 show_stack(NULL, NULL);
96} 96}
97EXPORT_SYMBOL(dump_stack); 97EXPORT_SYMBOL(dump_stack);
98
99#ifdef CONFIG_MMU
100void __bug(const char *file, int line, void *data)
101{
102 if (data)
103 printk(KERN_CRIT "kernel BUG at %s:%d (data = %p)!\n",
104 file, line, data);
105 else
106 printk(KERN_CRIT "kernel BUG at %s:%d!\n", file, line);
107
108 machine_halt();
109}
110
111int bad_trap(int trap_num, struct pt_regs *regs)
112{
113 printk(KERN_CRIT
114 "unimplemented trap %d called at 0x%08lx, pid %d!\n",
115 trap_num, regs->pc, current->pid);
116 return -ENOSYS;
117}
118
119int debug_trap(struct pt_regs *regs)
120{
121 int i;
122 printk(KERN_CRIT "debug trap\n");
123 for (i = 0; i < 32; i++) {
124 /* printk("r%i:%08X\t",i,regs->gpr[i]); */
125 if ((i % 4) == 3)
126 printk(KERN_CRIT "\n");
127 }
128 printk(KERN_CRIT "pc:%08lX\tmsr:%08lX\n", regs->pc, regs->msr);
129 return -ENOSYS;
130}
131#endif
diff --git a/arch/microblaze/kernel/vmlinux.lds.S b/arch/microblaze/kernel/vmlinux.lds.S
index 5ef619aad634..db72d7124602 100644
--- a/arch/microblaze/kernel/vmlinux.lds.S
+++ b/arch/microblaze/kernel/vmlinux.lds.S
@@ -24,7 +24,8 @@ SECTIONS {
24 .text : AT(ADDR(.text) - LOAD_OFFSET) { 24 .text : AT(ADDR(.text) - LOAD_OFFSET) {
25 _text = . ; 25 _text = . ;
26 _stext = . ; 26 _stext = . ;
27 *(.text .text.*) 27 HEAD_TEXT
28 TEXT_TEXT
28 *(.fixup) 29 *(.fixup)
29 EXIT_TEXT 30 EXIT_TEXT
30 EXIT_CALL 31 EXIT_CALL