|
|
1.1 ! root 1: /* ! 2: * Cisco 7200 (Predator) simulation platform. ! 3: * Copyright (c) 2005,2006 Christophe Fillot ([email protected]) ! 4: * ! 5: * Cisco C7200 (Predator) I/O FPGA: ! 6: * - Simulates a NMC93C46 Serial EEPROM as CPU and Midplane EEPROM. ! 7: * - Simulates a DALLAS DS1620 for Temperature Sensors. ! 8: * - Simulates voltage sensors. ! 9: * - Simulates console and AUX ports. ! 10: */ ! 11: ! 12: #include <stdio.h> ! 13: #include <stdlib.h> ! 14: #include <string.h> ! 15: #include <unistd.h> ! 16: #include <sys/types.h> ! 17: ! 18: #include <termios.h> ! 19: #include <fcntl.h> ! 20: #include <pthread.h> ! 21: ! 22: #include "ptask.h" ! 23: #include "mips64.h" ! 24: #include "dynamips.h" ! 25: #include "memory.h" ! 26: #include "device.h" ! 27: #include "dev_vtty.h" ! 28: #include "nmc93c46.h" ! 29: #include "ds1620.h" ! 30: #include "dev_c7200.h" ! 31: ! 32: /* Debugging flags */ ! 33: #define DEBUG_UNKNOWN 1 ! 34: #define DEBUG_LED 0 ! 35: #define DEBUG_IO_CTL 0 ! 36: #define DEBUG_ENVM 0 ! 37: ! 38: /* DUART RX/TX status (SRA/SRB) */ ! 39: #define DUART_RX_READY 0x01 ! 40: #define DUART_TX_READY 0x04 ! 41: ! 42: /* DUART RX/TX Interrupt Status/Mask */ ! 43: #define DUART_TXRDYA 0x01 ! 44: #define DUART_RXRDYA 0x02 ! 45: #define DUART_TXRDYB 0x10 ! 46: #define DUART_RXRDYB 0x20 ! 47: ! 48: /* Definitions for CPU and Midplane Serial EEPROMs */ ! 49: #define DO2_DATA_OUT_MIDPLANE 7 ! 50: #define DO1_DATA_OUT_CPU 6 ! 51: #define CS2_CHIP_SEL_MIDPLANE 5 ! 52: #define SK2_CLOCK_MIDPLANE 4 ! 53: #define DI2_DATA_IN_MIDPLANE 3 ! 54: #define CS1_CHIP_SEL_CPU 2 ! 55: #define SK1_CLOCK_CPU 1 ! 56: #define DI1_DATA_IN_CPU 0 ! 57: ! 58: /* Definitions for PEM (NPE-B) Serial EEPROM */ ! 59: #define DO1_DATA_OUT_PEM 3 ! 60: #define DI1_DATA_IN_PEM 2 ! 61: #define CS1_CHIP_SEL_PEM 1 ! 62: #define SK1_CLOCK_PEM 0 ! 63: ! 64: /* Pack the NVRAM */ ! 65: #define NVRAM_PACKED 0x04 ! 66: ! 67: /* 4 temperature sensors in a C7200 */ ! 68: #define C7200_TEMP_SENSORS 4 ! 69: #define C7200_DEFAULT_TEMP 22 /* default temperature: 22�C */ ! 70: ! 71: /* Voltages */ ! 72: #define C7200_A2D_SAMPLES 9 ! 73: ! 74: /* ! 75: * A2D MUX Select definitions. ! 76: */ ! 77: #define C7200_MUX_PS0 0x00 /* Power Supply 0 */ ! 78: #define C7200_MUX_PS1 0x02 /* Power Supply 1 */ ! 79: #define C7200_MUX_P3V 0x04 /* +3V */ ! 80: #define C7200_MUX_P12V 0x08 /* +12V */ ! 81: #define C7200_MUX_P5V 0x0a /* +5V */ ! 82: #define C7200_MUX_N12V 0x0c /* -12V */ ! 83: ! 84: /* Analog To Digital Converters samples */ ! 85: #define C7200_A2D_PS0 1150 ! 86: #define C7200_A2D_PS1 1150 ! 87: ! 88: /* Voltage Samples */ ! 89: #define C7200_A2D_P3V 1150 ! 90: #define C7200_A2D_P12V 1150 ! 91: #define C7200_A2D_P5V 1150 ! 92: #define C7200_A2D_N12V 1150 ! 93: ! 94: /* IO FPGA structure */ ! 95: struct iofpga_data { ! 96: u_int io_ctrl_reg; ! 97: ! 98: /* Managing CPU */ ! 99: cpu_mips_t *mgr_cpu; ! 100: ! 101: /* DUART & Console Management */ ! 102: u_int duart_interrupt; ! 103: pthread_t duart_con_thread; ! 104: pthread_t duart_aux_thread; ! 105: ! 106: /* Virtual TTY for Console and AUX ports */ ! 107: vtty_t *vtty_con,*vtty_aux; ! 108: ! 109: /* Temperature Control */ ! 110: u_int temp_cfg_reg[C7200_TEMP_SENSORS]; ! 111: u_int temp_deg_reg[C7200_TEMP_SENSORS]; ! 112: u_int temp_clk_low; ! 113: ! 114: u_int temp_cmd; ! 115: u_int temp_cmd_pos; ! 116: ! 117: u_int temp_data; ! 118: u_int temp_data_pos; ! 119: ! 120: /* Voltages */ ! 121: u_int mux; ! 122: }; ! 123: ! 124: /* Empty EEPROM */ ! 125: static unsigned short eeprom_empty_data[16] = { ! 126: 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, ! 127: 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, ! 128: }; ! 129: ! 130: /* CPU EEPROM definition */ ! 131: static struct nmc93c46_group_def eeprom_cpu_def = { ! 132: SK1_CLOCK_CPU, CS1_CHIP_SEL_CPU, ! 133: DI1_DATA_IN_CPU, DO1_DATA_OUT_CPU, ! 134: NULL, 0, ! 135: }; ! 136: ! 137: /* Midplane EEPROM definition */ ! 138: static struct nmc93c46_group_def eeprom_midplane_def = { ! 139: SK2_CLOCK_MIDPLANE, CS2_CHIP_SEL_MIDPLANE, ! 140: DI2_DATA_IN_MIDPLANE, DO2_DATA_OUT_MIDPLANE, ! 141: NULL, 0, ! 142: }; ! 143: ! 144: /* PEM (NPE-B) EEPROM definition */ ! 145: static struct nmc93c46_group_def eeprom_pem_def = { ! 146: SK1_CLOCK_PEM, CS1_CHIP_SEL_PEM, DI1_DATA_IN_PEM, DO1_DATA_OUT_PEM, ! 147: eeprom_empty_data, (sizeof(eeprom_empty_data) / 2), ! 148: }; ! 149: ! 150: /* IOFPGA manages simultaneously CPU and Midplane EEPROM */ ! 151: static struct nmc93c46_eeprom eeprom_cpu_midplane = { ! 152: 2, 0, "CPU and Midplane EEPROM", 0, ! 153: { &eeprom_cpu_def, &eeprom_midplane_def }, ! 154: { { 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0} }, ! 155: }; ! 156: ! 157: /* ! 158: * IOFPGA manages also PEM EEPROM (for NPE-B) ! 159: * PEM stands for "Power Entry Module": ! 160: * http://www.cisco.com/en/US/products/hw/routers/ps341/products_field_notice09186a00801cb26d.shtml ! 161: */ ! 162: static struct nmc93c46_eeprom eeprom_pem_npeb = { ! 163: 1, 0, "PEM (NPE-B) EEPROM", 0, { &eeprom_pem_def }, { { 0, 0, 0, 0, 0} }, ! 164: }; ! 165: ! 166: /* Reset DS1620 */ ! 167: static void temp_reset(struct iofpga_data *d) ! 168: { ! 169: d->temp_cmd_pos = 0; ! 170: d->temp_cmd = 0; ! 171: ! 172: d->temp_data_pos = 0; ! 173: d->temp_data = 0; ! 174: } ! 175: ! 176: /* Write the temperature control data */ ! 177: static void temp_write_ctrl(struct iofpga_data *d,u_char val) ! 178: { ! 179: switch(val) { ! 180: case DS1620_RESET_ON: ! 181: temp_reset(d); ! 182: break; ! 183: ! 184: case DS1620_CLK_LOW: ! 185: d->temp_clk_low = 1; ! 186: break; ! 187: ! 188: case DS1620_CLK_HIGH: ! 189: d->temp_clk_low = 0; ! 190: break; ! 191: } ! 192: } ! 193: ! 194: /* Read a temperature control data */ ! 195: static u_int temp_read_data(struct iofpga_data *d) ! 196: { ! 197: u_int i,data = 0; ! 198: ! 199: switch(d->temp_cmd) { ! 200: case DS1620_READ_CONFIG: ! 201: for(i=0;i<C7200_TEMP_SENSORS;i++) ! 202: data |= ((d->temp_cfg_reg[i] >> d->temp_data_pos) & 1) << i; ! 203: ! 204: d->temp_data_pos++; ! 205: ! 206: if (d->temp_data_pos == DS1620_CONFIG_READ_SIZE) ! 207: temp_reset(d); ! 208: ! 209: break; ! 210: ! 211: case DS1620_READ_TEMP: ! 212: for(i=0;i<C7200_TEMP_SENSORS;i++) ! 213: data |= ((d->temp_deg_reg[i] >> d->temp_data_pos) & 1) << i; ! 214: ! 215: d->temp_data_pos++; ! 216: ! 217: if (d->temp_data_pos == DS1620_DATA_READ_SIZE) ! 218: temp_reset(d); ! 219: ! 220: break; ! 221: ! 222: default: ! 223: m_log("IO_FPGA","temp_sensors: CMD = 0x%x\n",d->temp_cmd); ! 224: } ! 225: ! 226: return(data); ! 227: } ! 228: ! 229: /* Write the temperature data write register */ ! 230: static void temp_write_data(struct iofpga_data *d,u_char val) ! 231: { ! 232: if (val == DS1620_ENABLE_READ) { ! 233: d->temp_data_pos = 0; ! 234: return; ! 235: } ! 236: ! 237: if (!d->temp_clk_low) ! 238: return; ! 239: ! 240: /* Write a command */ ! 241: if (d->temp_cmd_pos < DS1620_WRITE_SIZE) ! 242: { ! 243: if (val == DS1620_DATA_HIGH) ! 244: d->temp_cmd |= 1 << d->temp_cmd_pos; ! 245: ! 246: d->temp_cmd_pos++; ! 247: ! 248: if (d->temp_cmd_pos == DS1620_WRITE_SIZE) { ! 249: switch(d->temp_cmd) { ! 250: case DS1620_START_CONVT: ! 251: //printf("temp_sensors: IOS enabled continuous monitoring.\n"); ! 252: temp_reset(d); ! 253: break; ! 254: case DS1620_READ_CONFIG: ! 255: case DS1620_READ_TEMP: ! 256: break; ! 257: default: ! 258: m_log("IO_FPGA","temp_sensors: IOS sent command 0x%x.\n", ! 259: d->temp_cmd); ! 260: } ! 261: } ! 262: } ! 263: else ! 264: { ! 265: if (val == DS1620_DATA_HIGH) ! 266: d->temp_data |= 1 << d->temp_data_pos; ! 267: ! 268: d->temp_data_pos++; ! 269: } ! 270: } ! 271: ! 272: /* Console port input thread */ ! 273: static void *tty_con_input(struct iofpga_data *d) ! 274: { ! 275: while(1) { ! 276: if (!vtty_read_and_store(d->vtty_con)) { ! 277: if (d->duart_interrupt & DUART_RXRDYA) ! 278: mips64_set_irq(d->mgr_cpu,C7200_DUART_IRQ); ! 279: } ! 280: } ! 281: ! 282: return NULL; ! 283: } ! 284: ! 285: /* AUX port input thread */ ! 286: static void *tty_aux_input(struct iofpga_data *d) ! 287: { ! 288: while(1) { ! 289: if (!vtty_read_and_store(d->vtty_aux)) { ! 290: if (d->duart_interrupt & DUART_RXRDYB) ! 291: mips64_set_irq(d->mgr_cpu,C7200_DUART_IRQ); ! 292: } ! 293: } ! 294: ! 295: return NULL; ! 296: } ! 297: ! 298: /* IRQ trickery for Console and AUX ports */ ! 299: static int tty_trigger_dummy_irq(struct iofpga_data *d,void *arg) ! 300: { ! 301: if (d->duart_interrupt & (DUART_TXRDYA|DUART_TXRDYB)) ! 302: mips64_set_irq(d->mgr_cpu,C7200_DUART_IRQ); ! 303: return(0); ! 304: } ! 305: ! 306: /* ! 307: * dev_iofpga_access() ! 308: */ ! 309: void *dev_iofpga_access(cpu_mips_t *cpu,struct vdevice *dev,m_uint32_t offset, ! 310: u_int op_size,u_int op_type,m_uint64_t *data) ! 311: { ! 312: struct iofpga_data *d = dev->priv_data; ! 313: u_char odata; ! 314: ! 315: if (op_type == MTS_READ) ! 316: *data = 0; ! 317: ! 318: switch(offset) { ! 319: /* I/O control register */ ! 320: case 0x204: ! 321: if (op_type == MTS_WRITE) { ! 322: #if DEBUG_IO_CTL ! 323: m_log("IO_FPGA: setting value 0x%llx in IO control register\n", ! 324: *data); ! 325: #endif ! 326: d->io_ctrl_reg = *data; ! 327: } ! 328: else { ! 329: *data = d->io_ctrl_reg; ! 330: *data |= NVRAM_PACKED; /* Packed NVRAM */ ! 331: } ! 332: break; ! 333: ! 334: /* CPU/Midplane EEPROMs */ ! 335: case 0x21c: ! 336: if (op_type == MTS_WRITE) ! 337: nmc93c46_write(&eeprom_cpu_midplane,(u_int)(*data)); ! 338: else ! 339: *data = nmc93c46_read(&eeprom_cpu_midplane); ! 340: break; ! 341: ! 342: /* PEM (NPE-B) EEPROM */ ! 343: case 0x388: ! 344: if (op_type == MTS_WRITE) ! 345: nmc93c46_write(&eeprom_pem_npeb,(u_int)(*data)); ! 346: else ! 347: *data = nmc93c46_read(&eeprom_pem_npeb); ! 348: break; ! 349: ! 350: /* Watchdog */ ! 351: case 0x234: ! 352: break; ! 353: ! 354: /* ! 355: * FPGA release/presence ? Flash SIMM size: ! 356: * 0x0001: 2048K Flash (2 banks) ! 357: * 0x0504: 8192K Flash (2 banks) ! 358: * 0x0704: 16384K Flash (2 banks) ! 359: * 0x2001: 1024K Flash (1 bank) ! 360: * 0x2504: 4096K Flash (1 bank) ! 361: * 0x2704: 8192K Flash (1 bank) ! 362: * ! 363: * Number of Flash SIMM banks + size. ! 364: * Touching some lower bits causes problems with environmental monitor. ! 365: * ! 366: * It is displayed by command "sh bootflash: chips" ! 367: */ ! 368: case 0x23c: ! 369: if (op_type == MTS_READ) ! 370: *data = 0x00002704; ! 371: break; ! 372: ! 373: /* LEDs */ ! 374: case 0x244: ! 375: #if DEBUG_LED ! 376: m_log("IO_FPGA","LED register is now 0x%x (0x%x)\n", ! 377: *data,(~*data) & 0x0F); ! 378: #endif ! 379: break; ! 380: ! 381: /* ==== DUART SCN2681 (console/aux) ==== */ ! 382: case 0x40c: /* Status Register A (SRA) */ ! 383: if (op_type == MTS_READ) { ! 384: odata = 0; ! 385: ! 386: if (vtty_is_char_avail(d->vtty_con)) ! 387: odata |= DUART_RX_READY; ! 388: ! 389: odata |= DUART_TX_READY; ! 390: ! 391: mips64_clear_irq(d->mgr_cpu,C7200_DUART_IRQ); ! 392: *data = odata; ! 393: } ! 394: break; ! 395: ! 396: case 0x414: /* Command Register A (CRA) */ ! 397: break; ! 398: ! 399: case 0x41c: /* RX/TX Holding Register A (RHRA/THRA) */ ! 400: if (op_type == MTS_WRITE) { ! 401: vtty_put_char(d->vtty_con,(char)*data); ! 402: } else { ! 403: *data = vtty_get_char(d->vtty_con); ! 404: } ! 405: break; ! 406: ! 407: case 0x42c: /* Interrupt Status/Mask Register (ISR/IMR) */ ! 408: if (op_type == MTS_WRITE) { ! 409: d->duart_interrupt = *data; ! 410: } else ! 411: *data = d->duart_interrupt; ! 412: break; ! 413: ! 414: case 0x44c: /* Status Register B (SRB) */ ! 415: if (op_type == MTS_READ) { ! 416: odata = 0; ! 417: ! 418: if (vtty_is_char_avail(d->vtty_aux)) ! 419: odata |= DUART_RX_READY; ! 420: ! 421: odata |= DUART_TX_READY; ! 422: ! 423: //mips64_clear_irq(d->mgr_cpu,C7200_DUART_IRQ); ! 424: *data = odata; ! 425: } ! 426: break; ! 427: ! 428: case 0x454: /* Command Register B (CRB) */ ! 429: break; ! 430: ! 431: case 0x45c: /* RX/TX Holding Register B (RHRB/THRB) */ ! 432: if (op_type == MTS_WRITE) { ! 433: vtty_put_char(d->vtty_aux,(char)*data); ! 434: } else { ! 435: *data = vtty_get_char(d->vtty_aux); ! 436: } ! 437: break; ! 438: ! 439: /* ==== DS 1620 (temp sensors) ==== */ ! 440: case 0x20c: /* Temperature Control */ ! 441: if (op_type == MTS_WRITE) ! 442: temp_write_ctrl(d,*data); ! 443: break; ! 444: ! 445: case 0x214: /* Temperature data write */ ! 446: if (op_type == MTS_WRITE) { ! 447: temp_write_data(d,*data); ! 448: d->mux = *data; ! 449: } ! 450: break; ! 451: ! 452: case 0x22c: /* Temperature data read */ ! 453: *data = temp_read_data(d); ! 454: break; ! 455: ! 456: case 0x257: /* ENVM A/D Converter */ ! 457: #if DEBUG_ENVM ! 458: m_log("ENVM","access to envm a/d converter - mux = %u\n",d->mux); ! 459: #endif ! 460: if (op_type == MTS_READ) { ! 461: switch(d->mux) { ! 462: case C7200_MUX_PS0: ! 463: *data = C7200_A2D_PS0; ! 464: break; ! 465: ! 466: case C7200_MUX_PS1: ! 467: *data = C7200_A2D_PS1; ! 468: break; ! 469: ! 470: case C7200_MUX_P3V: ! 471: *data = C7200_A2D_P3V; ! 472: break; ! 473: ! 474: case C7200_MUX_P12V: ! 475: *data = C7200_A2D_P12V; ! 476: break; ! 477: ! 478: case C7200_MUX_P5V: ! 479: *data = C7200_A2D_P5V; ! 480: break; ! 481: ! 482: case C7200_MUX_N12V: ! 483: *data = C7200_A2D_N12V; ! 484: break; ! 485: ! 486: default: ! 487: *data = 0; ! 488: } ! 489: ! 490: *data = *data / C7200_A2D_SAMPLES; ! 491: } ! 492: break; ! 493: ! 494: #if DEBUG_UNKNOWN ! 495: default: ! 496: if (op_type == MTS_WRITE) ! 497: m_log("IO_FPGA","read from addr 0x%x\n",offset); ! 498: else ! 499: m_log("IO_FPGA","write to addr 0x%x, value=0x%llx\n",offset,*data); ! 500: #endif ! 501: } ! 502: ! 503: return NULL; ! 504: } ! 505: ! 506: /* ! 507: * Set the base MAC address of the system. ! 508: */ ! 509: static int dev_iofpga_set_mac_addr(struct c7200_eeprom *mp_eeprom, ! 510: char *mac_addr) ! 511: { ! 512: m_eth_addr_t addr; ! 513: ! 514: if (parse_mac_addr(&addr,mac_addr) == -1) { ! 515: fprintf(stderr,"IO_FPGA: unable to parse MAC address '%s'\n",mac_addr); ! 516: return(-1); ! 517: } ! 518: ! 519: c7200_set_mac_addr(mp_eeprom,&addr); ! 520: return(0); ! 521: } ! 522: ! 523: /* ! 524: * dev_iofpga_init() ! 525: */ ! 526: int dev_iofpga_init(cpu_group_t *cpu_group,m_uint64_t paddr,m_uint32_t len, ! 527: char *npe,char *midplane,char *mac_addr) ! 528: { ! 529: struct c7200_eeprom *npe_eeprom,*mp_eeprom,*pem_eeprom; ! 530: struct iofpga_data *d; ! 531: struct vdevice *dev; ! 532: cpu_mips_t *cpu0; ! 533: u_int i; ! 534: ! 535: /* Device is managed by CPU0 */ ! 536: cpu0 = cpu_group_find_id(cpu_group,0); ! 537: ! 538: /* Set the NPE EEPROM */ ! 539: if (!(npe_eeprom = c7200_get_cpu_eeprom(npe))) { ! 540: fprintf(stderr,"C7200: unknown NPE \"%s\"!\n",npe); ! 541: return(-1); ! 542: } ! 543: ! 544: eeprom_cpu_def.data = npe_eeprom->data; ! 545: eeprom_cpu_def.data_len = npe_eeprom->len; ! 546: ! 547: /* Set the Midplane EEPROM */ ! 548: if (!(mp_eeprom = c7200_get_midplane_eeprom(midplane))) { ! 549: fprintf(stderr,"C7200: unknown Midplane \"%s\"!\n",midplane); ! 550: return(-1); ! 551: } ! 552: ! 553: eeprom_midplane_def.data = mp_eeprom->data; ! 554: eeprom_midplane_def.data_len = mp_eeprom->len; ! 555: ! 556: /* Set the PEM EEPROM for NPE-175/NPE-225 */ ! 557: if ((pem_eeprom = c7200_get_pem_eeprom(npe)) != NULL) { ! 558: eeprom_pem_def.data = pem_eeprom->data; ! 559: eeprom_pem_def.data_len = pem_eeprom->len; ! 560: } ! 561: ! 562: /* Set the base MAC address */ ! 563: if (mac_addr != NULL) { ! 564: dev_iofpga_set_mac_addr(mp_eeprom,mac_addr); ! 565: } else { ! 566: printf("C7200: Warning, no MAC address set.\n"); ! 567: } ! 568: ! 569: /* Allocate private data structure */ ! 570: if (!(d = malloc(sizeof(*d)))) { ! 571: fprintf(stderr,"IO_FPGA: out of memory\n"); ! 572: return(-1); ! 573: } ! 574: ! 575: memset(d,0,sizeof(*d)); ! 576: d->mgr_cpu = cpu0; ! 577: d->vtty_con = vtty_create("Console port",vtty_con_type,vtty_con_tcp_port); ! 578: d->vtty_aux = vtty_create("AUX port",vtty_aux_type,vtty_aux_tcp_port); ! 579: ! 580: for(i=0;i<C7200_TEMP_SENSORS;i++) { ! 581: d->temp_cfg_reg[i] = DS1620_CONFIG_STATUS_CPU; ! 582: d->temp_deg_reg[i] = C7200_DEFAULT_TEMP * 2; ! 583: } ! 584: ! 585: /* Create the device itself */ ! 586: if (!(dev = dev_create("io_fpga"))) { ! 587: fprintf(stderr,"IO_FPGA: unable to create device.\n"); ! 588: return(-1); ! 589: } ! 590: ! 591: dev->phys_addr = paddr; ! 592: dev->phys_len = len; ! 593: dev->handler = dev_iofpga_access; ! 594: dev->priv_data = d; ! 595: ! 596: /* Map this device to all CPU */ ! 597: cpu_group_bind_device(cpu_group,dev); ! 598: ! 599: /* Create console threads */ ! 600: if (vtty_con_type != VTTY_TYPE_NONE) ! 601: pthread_create(&d->duart_con_thread,NULL,(void *)tty_con_input,d); ! 602: ! 603: if (vtty_aux_type != VTTY_TYPE_NONE) ! 604: pthread_create(&d->duart_aux_thread,NULL,(void *)tty_aux_input,d); ! 605: ! 606: ptask_add((ptask_callback)tty_trigger_dummy_irq,d,NULL); ! 607: return(0); ! 608: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.