cvmx-app-init.c revision 215990
1/***********************license start*************** 2 * Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights 3 * reserved. 4 * 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are 8 * met: 9 * 10 * * Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * * Redistributions in binary form must reproduce the above 14 * copyright notice, this list of conditions and the following 15 * disclaimer in the documentation and/or other materials provided 16 * with the distribution. 17 18 * * Neither the name of Cavium Networks nor the names of 19 * its contributors may be used to endorse or promote products 20 * derived from this software without specific prior written 21 * permission. 22 23 * This Software, including technical data, may be subject to U.S. export control 24 * laws, including the U.S. Export Administration Act and its associated 25 * regulations, and may be subject to export or import regulations in other 26 * countries. 27 28 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" 29 * AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR 30 * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO 31 * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION OR 32 * DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM 33 * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, 34 * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF 35 * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR 36 * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR 37 * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. 38 ***********************license end**************************************/ 39 40 41 42 43 44 45 46#include <stdio.h> 47#include <stdint.h> 48#include <stdlib.h> 49#include <string.h> 50#include "cvmx-config.h" 51#include "cvmx.h" 52#include "cvmx-spinlock.h" 53#include <octeon-app-init.h> 54#include "cvmx-sysinfo.h" 55#include "cvmx-bootmem.h" 56#include "cvmx-uart.h" 57#include "cvmx-coremask.h" 58#include "cvmx-core.h" 59#include "cvmx-interrupt.h" 60#include "cvmx-ebt3000.h" 61#include "cvmx-sim-magic.h" 62#include "cvmx-debug.h" 63#include "../../bootloader/u-boot/include/octeon_mem_map.h" 64 65int cvmx_debug_uart = -1; 66 67/** 68 * @file 69 * 70 * Main entry point for all simple executive based programs. 71 */ 72 73 74extern void cvmx_interrupt_initialize(void); 75 76 77 78/** 79 * Main entry point for all simple executive based programs. 80 * This is the first C function called. It completes 81 * initialization, calls main, and performs C level cleanup. 82 * 83 * @param app_desc_addr 84 * Address of the application description structure passed 85 * brom the boot loader. 86 */ 87EXTERN_ASM void __cvmx_app_init(uint64_t app_desc_addr); 88 89 90/** 91 * Set up sysinfo structure from boot descriptor versions 6 and higher. 92 * In these versions, the interesting data in not in the boot info structure 93 * defined by the toolchain, but is in the cvmx_bootinfo structure defined in 94 * the simple exec. 95 * 96 * @param app_desc_ptr 97 * pointer to boot descriptor block 98 * 99 * @param sys_info_ptr 100 * pointer to sysinfo structure to fill in 101 */ 102static void process_boot_desc_ver_6(octeon_boot_descriptor_t *app_desc_ptr, cvmx_sysinfo_t *sys_info_ptr) 103{ 104 cvmx_bootinfo_t *cvmx_bootinfo_ptr = CASTPTR(cvmx_bootinfo_t, app_desc_ptr->cvmx_desc_vaddr); 105 106 /* copy application information for simple exec use */ 107 /* Populate the sys_info structure from the boot descriptor block created by the bootloader. 108 ** The boot descriptor block is put in the top of the heap, so it will be overwritten when the 109 ** heap is fully used. Information that is to be used must be copied before that. 110 ** Applications should only use the sys_info structure, not the boot descriptor 111 */ 112 if (cvmx_bootinfo_ptr->major_version == 1) 113 { 114 sys_info_ptr->core_mask = cvmx_bootinfo_ptr->core_mask; 115 sys_info_ptr->heap_base = cvmx_bootinfo_ptr->heap_base; 116 sys_info_ptr->heap_size = cvmx_bootinfo_ptr->heap_end - cvmx_bootinfo_ptr->heap_base; 117 sys_info_ptr->stack_top = cvmx_bootinfo_ptr->stack_top; 118 sys_info_ptr->stack_size = cvmx_bootinfo_ptr->stack_size; 119 sys_info_ptr->init_core = cvmx_get_core_num(); 120 sys_info_ptr->phy_mem_desc_addr = cvmx_bootinfo_ptr->phy_mem_desc_addr; 121 sys_info_ptr->exception_base_addr = cvmx_bootinfo_ptr->exception_base_addr; 122 sys_info_ptr->cpu_clock_hz = cvmx_bootinfo_ptr->eclock_hz; 123 sys_info_ptr->dram_data_rate_hz = cvmx_bootinfo_ptr->dclock_hz * 2; 124 125 sys_info_ptr->board_type = cvmx_bootinfo_ptr->board_type; 126 sys_info_ptr->board_rev_major = cvmx_bootinfo_ptr->board_rev_major; 127 sys_info_ptr->board_rev_minor = cvmx_bootinfo_ptr->board_rev_minor; 128 memcpy(sys_info_ptr->mac_addr_base, cvmx_bootinfo_ptr->mac_addr_base, 6); 129 sys_info_ptr->mac_addr_count = cvmx_bootinfo_ptr->mac_addr_count; 130 memcpy(sys_info_ptr->board_serial_number, cvmx_bootinfo_ptr->board_serial_number, CVMX_BOOTINFO_OCTEON_SERIAL_LEN); 131 sys_info_ptr->console_uart_num = 0; 132 if (cvmx_bootinfo_ptr->flags & OCTEON_BL_FLAG_CONSOLE_UART1) 133 sys_info_ptr->console_uart_num = 1; 134 135 if (cvmx_bootinfo_ptr->dram_size > 32*1024*1024) 136 sys_info_ptr->system_dram_size = (uint64_t)cvmx_bootinfo_ptr->dram_size; /* older bootloaders incorrectly gave this in bytes, so don't convert */ 137 else 138 sys_info_ptr->system_dram_size = (uint64_t)cvmx_bootinfo_ptr->dram_size * 1024 * 1024; /* convert from Megabytes to bytes */ 139 if (cvmx_bootinfo_ptr->minor_version >= 1) 140 { 141 sys_info_ptr->compact_flash_common_base_addr = cvmx_bootinfo_ptr->compact_flash_common_base_addr; 142 sys_info_ptr->compact_flash_attribute_base_addr = cvmx_bootinfo_ptr->compact_flash_attribute_base_addr; 143 sys_info_ptr->led_display_base_addr = cvmx_bootinfo_ptr->led_display_base_addr; 144 } 145 else if (sys_info_ptr->board_type == CVMX_BOARD_TYPE_EBT3000 || 146 sys_info_ptr->board_type == CVMX_BOARD_TYPE_EBT5800 || 147 sys_info_ptr->board_type == CVMX_BOARD_TYPE_EBT5810) 148 { 149 /* Default these variables so that users of structure can be the same no 150 ** matter what version fo boot info block the bootloader passes */ 151 sys_info_ptr->compact_flash_common_base_addr = 0x1d000000 + 0x800; 152 sys_info_ptr->compact_flash_attribute_base_addr = 0x1d010000; 153 if (sys_info_ptr->board_rev_major == 1) 154 sys_info_ptr->led_display_base_addr = 0x1d020000; 155 else 156 sys_info_ptr->led_display_base_addr = 0x1d020000 + 0xf8; 157 } 158 else 159 { 160 sys_info_ptr->compact_flash_common_base_addr = 0; 161 sys_info_ptr->compact_flash_attribute_base_addr = 0; 162 sys_info_ptr->led_display_base_addr = 0; 163 } 164 165 if (cvmx_bootinfo_ptr->minor_version >= 2) 166 { 167 sys_info_ptr->dfa_ref_clock_hz = cvmx_bootinfo_ptr->dfa_ref_clock_hz; 168 sys_info_ptr->bootloader_config_flags = cvmx_bootinfo_ptr->config_flags; 169 } 170 else 171 { 172 sys_info_ptr->dfa_ref_clock_hz = 0; 173 sys_info_ptr->bootloader_config_flags = 0; 174 if (app_desc_ptr->flags & OCTEON_BL_FLAG_DEBUG) 175 sys_info_ptr->bootloader_config_flags |= CVMX_BOOTINFO_CFG_FLAG_DEBUG; 176 if (app_desc_ptr->flags & OCTEON_BL_FLAG_NO_MAGIC) 177 sys_info_ptr->bootloader_config_flags |= CVMX_BOOTINFO_CFG_FLAG_NO_MAGIC; 178 } 179 180 } 181 else 182 { 183 printf("ERROR: Incompatible CVMX descriptor passed by bootloader: %d.%d\n", 184 (int)cvmx_bootinfo_ptr->major_version, (int)cvmx_bootinfo_ptr->minor_version); 185 exit(-1); 186 } 187} 188 189 190/** 191 * Interrupt handler for calling exit on Control-C interrupts. 192 * 193 * @param irq_number IRQ interrupt number 194 * @param registers CPU registers at the time of the interrupt 195 * @param user_arg Unused user argument 196 */ 197static void process_break_interrupt(int irq_number, uint64_t registers[32], void *user_arg) 198{ 199 /* Exclude new functionality when building with older toolchains */ 200#if OCTEON_APP_INIT_H_VERSION >= 3 201 int uart = irq_number - CVMX_IRQ_UART0; 202 cvmx_uart_lsr_t lsrval; 203 204 /* Check for a Control-C interrupt from the console. This loop will eat 205 all input received on the uart */ 206 lsrval.u64 = cvmx_read_csr(CVMX_MIO_UARTX_LSR(uart)); 207 while (lsrval.s.dr) 208 { 209 int c = cvmx_read_csr(CVMX_MIO_UARTX_RBR(uart)); 210 if (c == '\003') 211 { 212 register uint64_t tmp; 213 214 /* Wait for an another Control-C if right now we have no 215 access to the console. After this point we hold the 216 lock and use a different lock to synchronize between 217 the memfile dumps from different cores. As a 218 consequence regular printfs *don't* work after this 219 point! */ 220 if (__octeon_uart_trylock () == 1) 221 return; 222 223 /* Pulse MCD0 signal on Ctrl-C to stop all the cores. Also 224 set the MCD0 to be not masked by this core so we know 225 the signal is received by someone */ 226 asm volatile ( 227 "dmfc0 %0, $22\n" 228 "ori %0, %0, 0x1110\n" 229 "dmtc0 %0, $22\n" 230 : "=r" (tmp)); 231 } 232 lsrval.u64 = cvmx_read_csr(CVMX_MIO_UARTX_LSR(uart)); 233 } 234#endif 235} 236 237/** 238 * This is the debug exception handler with "break". Before calling exit to 239 * dump the profile-feedback output it releases the lock on the console. 240 * This way if there is buffered data in stdout it can still be flushed. 241 * stdio is required to flush all output during an fread. 242 */ 243 244static void exit_on_break(void) 245{ 246#if OCTEON_APP_INIT_H_VERSION >= 4 247 unsigned int coremask = cvmx_sysinfo_get()->core_mask; 248 249 cvmx_coremask_barrier_sync(coremask); 250 if (cvmx_coremask_first_core(coremask)) 251 __octeon_uart_unlock(); 252#endif 253 254 exit(0); 255} 256 257/* Add string signature to applications so that we can easily tell what 258** Octeon revision they were compiled for. Don't make static to avoid unused 259** variable warning. */ 260#define xstr(s) str(s) 261#define str(s) #s 262 263int octeon_model_version_check(uint32_t chip_id); 264 265#define OMS xstr(OCTEON_MODEL) 266char octeon_rev_signature[] = 267#ifdef USE_RUNTIME_MODEL_CHECKS 268 "Compiled for runtime Octeon model checking"; 269#else 270 "Compiled for Octeon processor id: "OMS; 271#endif 272 273void __cvmx_app_init(uint64_t app_desc_addr) 274{ 275 /* App descriptor used by bootloader */ 276 octeon_boot_descriptor_t *app_desc_ptr = CASTPTR(octeon_boot_descriptor_t, app_desc_addr); 277 278 /* app info structure used by the simple exec */ 279 cvmx_sysinfo_t *sys_info_ptr = cvmx_sysinfo_get(); 280 int breakflag = 0; 281 282 if (cvmx_coremask_first_core(app_desc_ptr->core_mask)) 283 { 284 /* do once per application setup */ 285 if (app_desc_ptr->desc_version < 6) 286 { 287 printf("Obsolete bootloader, can't run application\n"); 288 exit(-1); 289 } 290 else 291 { 292 /* Handle all newer versions here.... */ 293 if (app_desc_ptr->desc_version > 7) 294 { 295 printf("Warning: newer boot descripter version than expected\n"); 296 } 297 process_boot_desc_ver_6(app_desc_ptr,sys_info_ptr); 298 299 } 300 } 301 cvmx_coremask_barrier_sync(app_desc_ptr->core_mask); 302 303 breakflag = sys_info_ptr->bootloader_config_flags & CVMX_BOOTINFO_CFG_FLAG_BREAK; 304 305 /* No need to initialize bootmem, interrupts, interrupt handler and error handler 306 if version does not match. */ 307 if (cvmx_coremask_first_core(sys_info_ptr->core_mask)) 308 { 309 /* Check to make sure the Chip version matches the configured version */ 310 uint32_t chip_id = cvmx_get_proc_id(); 311 /* Make sure we can properly run on this chip */ 312 octeon_model_version_check(chip_id); 313 } 314 315 cvmx_interrupt_initialize(); 316 317 if (cvmx_coremask_first_core(sys_info_ptr->core_mask)) 318 { 319 int break_uart = 0; 320 unsigned int i; 321 322 /* Intialize the bootmem allocator with the descriptor that was provided by 323 * the bootloader 324 * IMPORTANT: All printfs must happen after this since PCI console uses named 325 * blocks. 326 */ 327 cvmx_bootmem_init(sys_info_ptr->phy_mem_desc_addr); 328 if (breakflag && cvmx_debug_booted()) 329 { 330 printf("ERROR: Using debug and break together in not supported.\n"); 331 while (1) 332 ; 333 } 334 335 /* Search through the arguments for a break=X or a debug=X. */ 336 for (i = 0; i < app_desc_ptr->argc; i++) 337 { 338 const char *argv = CASTPTR(const char, CVMX_ADD_SEG32(CVMX_MIPS32_SPACE_KSEG0, app_desc_ptr->argv[i])); 339 if (strncmp(argv, "break=", 6) == 0) 340 break_uart = atoi(argv + 6); 341 else if (strncmp(argv, "debug=", 6) == 0) 342 cvmx_debug_uart = atoi(argv + 6); 343 } 344 345 if (breakflag) 346 { 347 int32_t *trampoline = CASTPTR(int32_t, CVMX_ADD_SEG32(CVMX_MIPS32_SPACE_KSEG0, BOOTLOADER_DEBUG_TRAMPOLINE)); 348 /* On debug exception, call exit_on_break from all cores. */ 349 *trampoline = (int32_t)(long)&exit_on_break; 350 cvmx_uart_enable_intr(break_uart, process_break_interrupt); 351 } 352 } 353 354 cvmx_coremask_barrier_sync(app_desc_ptr->core_mask); 355 356 /* Clear BEV now that we have installed exception handlers. */ 357 uint64_t tmp; 358 asm volatile ( 359 " .set push \n" 360 " .set mips64 \n" 361 " .set noreorder \n" 362 " .set noat \n" 363 " mfc0 %[tmp], $12, 0 \n" 364 " li $at, 1 << 22 \n" 365 " not $at, $at \n" 366 " and %[tmp], $at \n" 367 " mtc0 %[tmp], $12, 0 \n" 368 " .set pop \n" 369 : [tmp] "=&r" (tmp) : ); 370 371 /* Set all cores to stop on MCD0 signals */ 372 asm volatile( 373 "dmfc0 %0, $22, 0\n" 374 "or %0, %0, 0x1100\n" 375 "dmtc0 %0, $22, 0\n" : "=r" (tmp)); 376 377 CVMX_SYNC; 378 379 /* Now intialize the debug exception handler as BEV is cleared. */ 380 if (!breakflag) 381 cvmx_debug_init(); 382 383 /* Synchronise all cores at this point */ 384 cvmx_coremask_barrier_sync(app_desc_ptr->core_mask); 385 386} 387 388int cvmx_user_app_init(void) 389{ 390 uint64_t bist_val; 391 uint64_t mask; 392 int bist_errors = 0; 393 uint64_t tmp; 394 uint64_t base_addr; 395 396 397 /* Put message on LED display */ 398 if (cvmx_sysinfo_get()->board_type != CVMX_BOARD_TYPE_SIM) 399 ebt3000_str_write("CVMX "); 400 401 /* Check BIST results for COP0 registers, some values only meaningful in pass 2 */ 402 CVMX_MF_CACHE_ERR(bist_val); 403 mask = (1ULL<<32) | (1ULL<<33) | (1ULL<<34) | (1ULL<<35) | (1ULL<<36); 404 bist_val &= mask; 405 if (bist_val) 406 { 407 printf("BIST FAILURE: COP0_CACHE_ERR: 0x%llx\n", (unsigned long long)bist_val); 408 bist_errors++; 409 } 410 411 mask = 0xfc00000000000000ull; 412 CVMX_MF_CVM_MEM_CTL(bist_val); 413 bist_val &= mask; 414 if (bist_val) 415 { 416 printf("BIST FAILURE: COP0_CVM_MEM_CTL: 0x%llx\n", (unsigned long long)bist_val); 417 bist_errors++; 418 } 419 420 /* Set up 4 cache lines of local memory, make available from Kernel space */ 421 CVMX_MF_CVM_MEM_CTL(tmp); 422 tmp &= ~0x1ffull; 423 tmp |= 0x104ull; 424 /* Set WBTHRESH=4 as per Core-14752 errata in cn63xxp1.X. */ 425 if (OCTEON_IS_MODEL(OCTEON_CN63XX_PASS1_X)) 426 { 427 tmp &= ~(0xfull << 11); 428 tmp |= 4 << 11; 429 } 430 CVMX_MT_CVM_MEM_CTL(tmp); 431 432 433#if CVMX_USE_1_TO_1_TLB_MAPPINGS 434 435 /* Check to see if the bootloader is indicating that the application is outside 436 ** of the 0x10000000 0x20000000 range, in which case we can't use 1-1 mappings */ 437 if (cvmx_sysinfo_get()->bootloader_config_flags & CVMX_BOOTINFO_CFG_FLAG_OVERSIZE_TLB_MAPPING) 438 { 439 printf("ERROR: 1-1 TLB mappings configured and oversize application loaded.\n"); 440 printf("ERROR: Either 1-1 TLB mappings must be disabled or application size reduced.\n"); 441 exit(-1); 442 } 443 444 /* Create 1-1 Mappings for all DRAM up to 8 gigs, excluding the low 1 Megabyte. This area 445 ** is reserved for the bootloader and exception vectors. By not mapping this area, NULL pointer 446 ** dereferences will be caught with TLB exceptions. Exception handlers should be written 447 ** using XKPHYS or KSEG0 addresses. */ 448#if CVMX_NULL_POINTER_PROTECT 449 /* Exclude low 1 MByte from mapping to detect NULL pointer accesses. 450 ** The only down side of this is it uses more TLB mappings */ 451 cvmx_core_add_fixed_tlb_mapping_bits(0x0, 0x0, 0x100000 | TLB_DIRTY | TLB_VALID | TLB_GLOBAL, CVMX_TLB_PAGEMASK_1M); 452 cvmx_core_add_fixed_tlb_mapping(0x200000, 0x200000, 0x300000, CVMX_TLB_PAGEMASK_1M); 453 cvmx_core_add_fixed_tlb_mapping(0x400000, 0x400000, 0x500000, CVMX_TLB_PAGEMASK_1M); 454 cvmx_core_add_fixed_tlb_mapping(0x600000, 0x600000, 0x700000, CVMX_TLB_PAGEMASK_1M); 455 456 cvmx_core_add_fixed_tlb_mapping(0x800000, 0x800000, 0xC00000, CVMX_TLB_PAGEMASK_4M); 457 cvmx_core_add_fixed_tlb_mapping(0x1000000, 0x1000000, 0x1400000, CVMX_TLB_PAGEMASK_4M); 458 cvmx_core_add_fixed_tlb_mapping(0x1800000, 0x1800000, 0x1c00000, CVMX_TLB_PAGEMASK_4M); 459 460 cvmx_core_add_fixed_tlb_mapping(0x2000000, 0x2000000, 0x3000000, CVMX_TLB_PAGEMASK_16M); 461 cvmx_core_add_fixed_tlb_mapping(0x4000000, 0x4000000, 0x5000000, CVMX_TLB_PAGEMASK_16M); 462 cvmx_core_add_fixed_tlb_mapping(0x6000000, 0x6000000, 0x7000000, CVMX_TLB_PAGEMASK_16M); 463#else 464 /* Map entire low 128 Megs, including 0x0 */ 465 cvmx_core_add_fixed_tlb_mapping(0x0, 0x0, 0x4000000ULL, CVMX_TLB_PAGEMASK_64M); 466#endif 467 cvmx_core_add_fixed_tlb_mapping(0x8000000ULL, 0x8000000ULL, 0xc000000ULL, CVMX_TLB_PAGEMASK_64M); 468 469 if (OCTEON_IS_MODEL(OCTEON_CN6XXX)) 470 { 471 for (base_addr = 0x20000000ULL; base_addr < (cvmx_sysinfo_get()->system_dram_size + 0x10000000ULL); base_addr += 0x20000000ULL) 472 { 473 if (0 > cvmx_core_add_fixed_tlb_mapping(base_addr, base_addr, base_addr + 0x10000000ULL, CVMX_TLB_PAGEMASK_256M)) 474 { 475 printf("ERROR adding 1-1 TLB mapping for address 0x%llx\n", (unsigned long long)base_addr); 476 /* Exit from here, as expected memory mappings aren't set 477 up if this fails */ 478 exit(-1); 479 } 480 } 481 } 482 else 483 { 484 /* Create 1-1 mapping for next 256 megs 485 ** bottom page is not valid */ 486 cvmx_core_add_fixed_tlb_mapping_bits(0x400000000ULL, 0, 0x410000000ULL | TLB_DIRTY | TLB_VALID | TLB_GLOBAL, CVMX_TLB_PAGEMASK_256M); 487 488 /* Map from 0.5 up to the installed memory size in 512 MByte chunks. If this loop runs out of memory, 489 ** the NULL pointer detection can be disabled to free up more TLB entries. */ 490 if (cvmx_sysinfo_get()->system_dram_size > 0x20000000ULL) 491 { 492 for (base_addr = 0x20000000ULL; base_addr <= (cvmx_sysinfo_get()->system_dram_size - 0x20000000ULL); base_addr += 0x20000000ULL) 493 { 494 if (0 > cvmx_core_add_fixed_tlb_mapping(base_addr, base_addr, base_addr + 0x10000000ULL, CVMX_TLB_PAGEMASK_256M)) 495 { 496 printf("ERROR adding 1-1 TLB mapping for address 0x%llx\n", (unsigned long long)base_addr); 497 /* Exit from here, as expected memory mappings 498 aren't set up if this fails */ 499 exit(-1); 500 } 501 } 502 } 503 } 504#endif 505 506 507 cvmx_sysinfo_t *sys_info_ptr = cvmx_sysinfo_get(); 508 cvmx_bootmem_init(sys_info_ptr->phy_mem_desc_addr); 509 510 return(0); 511} 512 513void __cvmx_app_exit(void) 514{ 515 cvmx_debug_finish(); 516 517 if (cvmx_sysinfo_get()->board_type == CVMX_BOARD_TYPE_SIM) 518 { 519 CVMX_BREAK; 520 } 521 /* Hang forever, until more appropriate stand alone simple executive 522 exit() is implemented */ 523 524 while (1); 525} 526 527 528 529