From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1GmWrh-0008So-FI for qemu-devel@nongnu.org; Tue, 21 Nov 2006 09:44:49 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1GmWrg-0008SE-3s for qemu-devel@nongnu.org; Tue, 21 Nov 2006 09:44:48 -0500 Received: from [199.232.76.173] (helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1GmWrf-0008S8-Tv for qemu-devel@nongnu.org; Tue, 21 Nov 2006 09:44:48 -0500 Received: from [193.7.176.60] (helo=mail.bawue.net) by monty-python.gnu.org with esmtps (TLS-1.0:DHE_RSA_AES_256_CBC_SHA:32) (Exim 4.52) id 1GmWre-00027Z-OU for qemu-devel@nongnu.org; Tue, 21 Nov 2006 09:44:47 -0500 Received: from lagash (unknown [195.71.97.210]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.bawue.net (Postfix) with ESMTP id 5C951BA4A3 for ; Tue, 21 Nov 2006 15:44:46 +0100 (CET) Received: from ths by lagash with local (Exim 4.63) (envelope-from ) id 1GmWqR-0003Xl-UU for qemu-devel@nongnu.org; Tue, 21 Nov 2006 14:43:31 +0000 Date: Tue, 21 Nov 2006 14:43:31 +0000 Message-ID: <20061121144331.GE12745@networkno.de> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="q9KOos5vDmpwPx9o" Content-Disposition: inline From: Thiemo Seufer Subject: [Qemu-devel] [PATCH 4/6] A cumulative MIPS patchset Reply-To: qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org --q9KOos5vDmpwPx9o Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hello All, this patch inlines mfc0 instructions for the MIPS system emulation. Thiemo Index: qemu-work/target-mips/exec.h =================================================================== --- qemu-work.orig/target-mips/exec.h 2006-11-15 22:41:21.000000000 +0000 +++ qemu-work/target-mips/exec.h 2006-11-15 22:41:24.000000000 +0000 @@ -66,7 +66,8 @@ void do_msub (void); void do_msubu (void); #endif -void do_mfc0(int reg, int sel); +void do_mfc0_random(void); +void do_mfc0_count(void); void do_mtc0(int reg, int sel); void do_tlbwi (void); void do_tlbwr (void); Index: qemu-work/target-mips/op.c =================================================================== --- qemu-work.orig/target-mips/op.c 2006-11-15 22:41:18.000000000 +0000 +++ qemu-work/target-mips/op.c 2006-11-15 22:41:24.000000000 +0000 @@ -688,9 +688,167 @@ } /* CP0 functions */ -void op_mfc0 (void) +void op_mfc0_index (void) { - CALL_FROM_TB2(do_mfc0, PARAM1, PARAM2); + T0 = env->CP0_index; + RETURN(); +} + +void op_mfc0_random (void) +{ + CALL_FROM_TB0(do_mfc0_random); + RETURN(); +} + +void op_mfc0_entrylo0 (void) +{ + T0 = env->CP0_EntryLo0; + RETURN(); +} + +void op_mfc0_entrylo1 (void) +{ + T0 = env->CP0_EntryLo1; + RETURN(); +} + +void op_mfc0_context (void) +{ + T0 = env->CP0_Context; + RETURN(); +} + +void op_mfc0_pagemask (void) +{ + T0 = env->CP0_PageMask; + RETURN(); +} + +void op_mfc0_wired (void) +{ + T0 = env->CP0_Wired; + RETURN(); +} + +void op_mfc0_badvaddr (void) +{ + T0 = env->CP0_BadVAddr; + RETURN(); +} + +void op_mfc0_count (void) +{ + CALL_FROM_TB0(do_mfc0_count); + RETURN(); +} + +void op_mfc0_entryhi (void) +{ + T0 = env->CP0_EntryHi; + RETURN(); +} + +void op_mfc0_compare (void) +{ + T0 = env->CP0_Compare; + RETURN(); +} + +void op_mfc0_status (void) +{ + T0 = env->CP0_Status; + if (env->hflags & MIPS_HFLAG_UM) + T0 |= (1 << CP0St_UM); + if (env->hflags & MIPS_HFLAG_ERL) + T0 |= (1 << CP0St_ERL); + if (env->hflags & MIPS_HFLAG_EXL) + T0 |= (1 << CP0St_EXL); + RETURN(); +} + +void op_mfc0_cause (void) +{ + T0 = env->CP0_Cause; + RETURN(); +} + +void op_mfc0_epc (void) +{ + T0 = env->CP0_EPC; + RETURN(); +} + +void op_mfc0_prid (void) +{ + T0 = env->CP0_PRid; + RETURN(); +} + +void op_mfc0_config0 (void) +{ + T0 = env->CP0_Config0; + RETURN(); +} + +void op_mfc0_config1 (void) +{ + T0 = env->CP0_Config1; + RETURN(); +} + +void op_mfc0_lladdr (void) +{ + T0 = env->CP0_LLAddr >> 4; + RETURN(); +} + +void op_mfc0_watchlo (void) +{ + T0 = env->CP0_WatchLo; + RETURN(); +} + +void op_mfc0_watchhi (void) +{ + T0 = env->CP0_WatchHi; + RETURN(); +} + +void op_mfc0_debug (void) +{ + T0 = env->CP0_Debug; + if (env->hflags & MIPS_HFLAG_DM) + T0 |= 1 << CP0DB_DM; + RETURN(); +} + +void op_mfc0_depc (void) +{ + T0 = env->CP0_DEPC; + RETURN(); +} + +void op_mfc0_taglo (void) +{ + T0 = env->CP0_TagLo; + RETURN(); +} + +void op_mfc0_datalo (void) +{ + T0 = env->CP0_DataLo; + RETURN(); +} + +void op_mfc0_errorepc (void) +{ + T0 = env->CP0_ErrorEPC; + RETURN(); +} + +void op_mfc0_desave (void) +{ + T0 = env->CP0_DESAVE; RETURN(); } Index: qemu-work/target-mips/op_helper.c =================================================================== --- qemu-work.orig/target-mips/op_helper.c 2006-11-15 22:41:04.000000000 +0000 +++ qemu-work/target-mips/op_helper.c 2006-11-15 22:41:24.000000000 +0000 @@ -132,10 +132,16 @@ #endif #if defined(CONFIG_USER_ONLY) -void do_mfc0 (int reg, int sel) +void do_mfc0_random (void) { - cpu_abort(env, "mfc0 reg=%d sel=%d\n", reg, sel); + cpu_abort(env, "mfc0 random\n"); } + +void do_mfc0_count (void) +{ + cpu_abort(env, "mfc0 count\n"); +} + void do_mtc0 (int reg, int sel) { cpu_abort(env, "mtc0 reg=%d sel=%d\n", reg, sel); @@ -160,152 +166,18 @@ { cpu_abort(env, "tlbr\n"); } + #else /* CP0 helpers */ -void do_mfc0 (int reg, int sel) +void do_mfc0_random (void) { - const unsigned char *rn; + T0 = cpu_mips_get_random(env); +} - if (sel != 0 && reg != 16 && reg != 28) { - rn = "invalid"; - goto print; - } - switch (reg) { - case 0: - T0 = env->CP0_index; - rn = "Index"; - break; - case 1: - T0 = cpu_mips_get_random(env); - rn = "Random"; - break; - case 2: - T0 = env->CP0_EntryLo0; - rn = "EntryLo0"; - break; - case 3: - T0 = env->CP0_EntryLo1; - rn = "EntryLo1"; - break; - case 4: - T0 = env->CP0_Context; - rn = "Context"; - break; - case 5: - T0 = env->CP0_PageMask; - rn = "PageMask"; - break; - case 6: - T0 = env->CP0_Wired; - rn = "Wired"; - break; - case 8: - T0 = env->CP0_BadVAddr; - rn = "BadVaddr"; - break; - case 9: - T0 = cpu_mips_get_count(env); - rn = "Count"; - break; - case 10: - T0 = env->CP0_EntryHi; - rn = "EntryHi"; - break; - case 11: - T0 = env->CP0_Compare; - rn = "Compare"; - break; - case 12: - T0 = env->CP0_Status; - if (env->hflags & MIPS_HFLAG_UM) - T0 |= (1 << CP0St_UM); - rn = "Status"; - break; - case 13: - T0 = env->CP0_Cause; - rn = "Cause"; - break; - case 14: - T0 = env->CP0_EPC; - rn = "EPC"; - break; - case 15: - T0 = env->CP0_PRid; - rn = "PRid"; - break; - case 16: - switch (sel) { - case 0: - T0 = env->CP0_Config0; - rn = "Config"; - break; - case 1: - T0 = env->CP0_Config1; - rn = "Config1"; - break; - default: - rn = "Unknown config register"; - break; - } - break; - case 17: - T0 = env->CP0_LLAddr >> 4; - rn = "LLAddr"; - break; - case 18: - T0 = env->CP0_WatchLo; - rn = "WatchLo"; - break; - case 19: - T0 = env->CP0_WatchHi; - rn = "WatchHi"; - break; - case 23: - T0 = env->CP0_Debug; - if (env->hflags & MIPS_HFLAG_DM) - T0 |= 1 << CP0DB_DM; - rn = "Debug"; - break; - case 24: - T0 = env->CP0_DEPC; - rn = "DEPC"; - break; - case 28: - switch (sel) { - case 0: - T0 = env->CP0_TagLo; - rn = "TagLo"; - break; - case 1: - T0 = env->CP0_DataLo; - rn = "DataLo"; - break; - default: - rn = "unknown sel"; - break; - } - break; - case 30: - T0 = env->CP0_ErrorEPC; - rn = "ErrorEPC"; - break; - case 31: - T0 = env->CP0_DESAVE; - rn = "DESAVE"; - break; - default: - rn = "unknown"; - break; - } - print: -#if defined MIPS_DEBUG_DISAS - if (loglevel & CPU_LOG_TB_IN_ASM) { - fprintf(logfile, "%08x mfc0 %s => %08x (%d %d)\n", - env->PC, rn, T0, reg, sel); - } -#endif - return; +void do_mfc0_count (void) +{ + T0 = cpu_mips_get_count(env); } void do_mtc0 (int reg, int sel) Index: qemu-work/target-mips/translate.c =================================================================== --- qemu-work.orig/target-mips/translate.c 2006-11-15 22:41:18.000000000 +0000 +++ qemu-work/target-mips/translate.c 2006-11-15 22:41:24.000000000 +0000 @@ -1349,6 +1349,155 @@ } /* CP0 (MMU and control) */ +static void gen_mfc0 (DisasContext *ctx, int reg, int sel) +{ + const unsigned char *rn; + + if (sel != 0 && reg != 16 && reg != 28) { + rn = "invalid"; + goto die; + } + switch (reg) { + case 0: + gen_op_mfc0_index(); + rn = "Index"; + break; + case 1: + gen_op_mfc0_random(); + rn = "Random"; + break; + case 2: + gen_op_mfc0_entrylo0(); + rn = "EntryLo0"; + break; + case 3: + gen_op_mfc0_entrylo1(); + rn = "EntryLo1"; + break; + case 4: + gen_op_mfc0_context(); + rn = "Context"; + break; + case 5: + gen_op_mfc0_pagemask(); + rn = "PageMask"; + break; + case 6: + gen_op_mfc0_wired(); + rn = "Wired"; + break; + case 8: + gen_op_mfc0_badvaddr(); + rn = "BadVaddr"; + break; + case 9: + gen_op_mfc0_count(); + rn = "Count"; + break; + case 10: + gen_op_mfc0_entryhi(); + rn = "EntryHi"; + break; + case 11: + gen_op_mfc0_compare(); + rn = "Compare"; + break; + case 12: + gen_op_mfc0_status(); + rn = "Status"; + break; + case 13: + gen_op_mfc0_cause(); + rn = "Cause"; + break; + case 14: + gen_op_mfc0_epc(); + rn = "EPC"; + break; + case 15: + gen_op_mfc0_prid(); + rn = "PRid"; + break; + case 16: + switch (sel) { + case 0: + gen_op_mfc0_config0(); + rn = "Config"; + break; + case 1: + gen_op_mfc0_config1(); + rn = "Config1"; + break; + default: + rn = "Unknown config register"; + goto die; + } + break; + case 17: + gen_op_mfc0_lladdr(); + rn = "LLAddr"; + break; + case 18: + gen_op_mfc0_watchlo(); + rn = "WatchLo"; + break; + case 19: + gen_op_mfc0_watchhi(); + rn = "WatchHi"; + break; + case 23: + gen_op_mfc0_debug(); + rn = "Debug"; + break; + case 24: + gen_op_mfc0_depc(); + rn = "DEPC"; + break; + case 28: + switch (sel) { + case 0: + gen_op_mfc0_taglo(); + rn = "TagLo"; + break; + case 1: + gen_op_mfc0_datalo(); + rn = "DataLo"; + break; + default: + rn = "unknown sel"; + goto die; + } + break; + case 30: + gen_op_mfc0_errorepc(); + rn = "ErrorEPC"; + break; + case 31: + gen_op_mfc0_desave(); + rn = "DESAVE"; + break; + default: + rn = "unknown"; + goto die; + } +#if defined MIPS_DEBUG_DISAS + if (loglevel & CPU_LOG_TB_IN_ASM) { + fprintf(logfile, "%08x mfc0 %s => %08x (%d %d)\n", + env->PC, rn, T0, reg, sel); + } +#endif + return; + +die: +#if defined MIPS_DEBUG_DISAS + if (loglevel & CPU_LOG_TB_IN_ASM) { + fprintf(logfile, "%08x mfc0 %s => %08x (%d %d)\n", + env->PC, rn, T0, reg, sel); + } +#endif + generate_exception(ctx, EXCP_RI); +} + static void gen_cp0 (DisasContext *ctx, uint16_t opc, int rt, int rd) { const unsigned char *opn = "unk"; @@ -1370,7 +1519,7 @@ /* Treat as NOP */ return; } - gen_op_mfc0(rd, ctx->opcode & 0x7); + gen_mfc0(ctx, rd, ctx->opcode & 0x7); gen_op_store_T0_gpr(rt); opn = "mfc0"; break; --q9KOos5vDmpwPx9o Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="qemu.drow-halt-reboot" Content-Transfer-Encoding: quoted-printable Date: Sat, 11 Nov 2006 20:02:40 -0500 =46rom: Daniel Jacobowitz To: qemu-devel@nongnu.org Subject: [Qemu-devel] MIPS halt / reboot The MIPS kernel already has a reset/halt device for qemu (arch/mips/qemu/q-reset.c). I have no idea at all where this came =66rom, but hey, it seems sensible enough to me so I implemented it in qemu. This requires a bit of additional mucking around, since MIPS doesn't currently have any BIOS support, so I made the reset vector reload any specified kernel and initrd. Works in my Debian rootfs; very handy for automated benchmarking. --=20 Daniel Jacobowitz CodeSourcery --- hw/mips_r4k.c | 144 +++++++++++++++++++++++++++++++++----------= ----- target-mips/cpu.h | 8 ++ target-mips/translate.c | 10 +++ 3 files changed, 116 insertions(+), 46 deletions(-) Index: qemu-work/hw/mips_r4k.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- qemu-work.orig/hw/mips_r4k.c 2006-11-15 22:40:28.000000000 +0000 +++ qemu-work/hw/mips_r4k.c 2006-11-15 22:41:14.000000000 +0000 @@ -110,6 +110,93 @@ cpu_mips_update_count(env, 1, 0); } =20 +static void mips_qemu_writel (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + if ((addr & 0xffff) =3D=3D 0 && val =3D=3D 42) + qemu_system_reset_request (); + else if ((addr & 0xffff) =3D=3D 4 && val =3D=3D 42) + qemu_system_shutdown_request (); +} + +static uint32_t mips_qemu_readl (void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static CPUWriteMemoryFunc *mips_qemu_write[] =3D { + &mips_qemu_writel, + &mips_qemu_writel, + &mips_qemu_writel, +}; + +static CPUReadMemoryFunc *mips_qemu_read[] =3D { + &mips_qemu_readl, + &mips_qemu_readl, + &mips_qemu_readl, +}; + +static int mips_qemu_iomemtype =3D 0; + +void load_kernel (CPUState *env, int ram_size, const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename) +{ + int64_t entry =3D 0; + long kernel_size, initrd_size; + + kernel_size =3D load_elf(kernel_filename, VIRT_TO_PHYS_ADDEND, &entry); + if (kernel_size >=3D 0) + env->PC =3D entry; + else { + kernel_size =3D load_image(kernel_filename, + phys_ram_base + KERNEL_LOAD_ADDR + VIRT_T= O_PHYS_ADDEND); + if (kernel_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + kernel_filename); + exit(1); + } + env->PC =3D KERNEL_LOAD_ADDR; + } + + /* load initrd */ + initrd_size =3D 0; + if (initrd_filename) { + initrd_size =3D load_image(initrd_filename, + phys_ram_base + INITRD_LOAD_ADDR + VIRT_T= O_PHYS_ADDEND); + if (initrd_size =3D=3D (target_ulong) -1) { + fprintf(stderr, "qemu: could not load initial ram disk '%s'\n", + initrd_filename); + exit(1); + } + } + + /* Store command line. */ + if (initrd_size > 0) { + int ret; + ret =3D sprintf(phys_ram_base + (16 << 20) - 256, + "rd_start=3D0x%08x rd_size=3D%li ", + INITRD_LOAD_ADDR, + initrd_size); + strcpy (phys_ram_base + (16 << 20) - 256 + ret, kernel_cmdline); + } + else { + strcpy (phys_ram_base + (16 << 20) - 256, kernel_cmdline); + } + + *(int *)(phys_ram_base + (16 << 20) - 260) =3D tswap32 (0x12345678); + *(int *)(phys_ram_base + (16 << 20) - 264) =3D tswap32 (ram_size); +} + +static void main_cpu_reset(void *opaque) +{ + CPUState *env =3D opaque; + cpu_reset(env); + + if (env->kernel_filename) + load_kernel (env, env->ram_size, env->kernel_filename, + env->kernel_cmdline, env->initrd_filename); +} =20 void mips_r4k_init (int ram_size, int vga_ram_size, int boot_device, DisplayState *ds, const char **fd_filename, int snapsh= ot, @@ -117,19 +204,24 @@ const char *initrd_filename) { char buf[1024]; - int64_t entry =3D 0; unsigned long bios_offset; int ret; CPUState *env; - long kernel_size; int i; =20 env =3D cpu_init(); register_savevm("cpu", 0, 3, cpu_save, cpu_load, env); + qemu_register_reset(main_cpu_reset, env); =20 /* allocate RAM */ cpu_register_physical_memory(0, ram_size, IO_MEM_RAM); =20 + if (!mips_qemu_iomemtype) { + mips_qemu_iomemtype =3D cpu_register_io_memory(0, mips_qemu_read, + mips_qemu_write, NULL); + } + cpu_register_physical_memory(0x1fbf0000, 0x10000, mips_qemu_iomemtype); + /* Try to load a BIOS image. If this fails, we continue regardless, but initialize the hardware ourselves. When a kernel gets preloaded we also initialize the hardware, since the BIOS wasn't @@ -146,38 +238,13 @@ buf); } =20 - kernel_size =3D 0; if (kernel_filename) { - kernel_size =3D load_elf(kernel_filename, VIRT_TO_PHYS_ADDEND, &entry); - if (kernel_size >=3D 0) - env->PC =3D entry; - else { - kernel_size =3D load_image(kernel_filename, - phys_ram_base + KERNEL_LOAD_ADDR + VI= RT_TO_PHYS_ADDEND); - if (kernel_size < 0) { - fprintf(stderr, "qemu: could not load kernel '%s'\n", - kernel_filename); - exit(1); - } - env->PC =3D KERNEL_LOAD_ADDR; - } - - /* load initrd */ - if (initrd_filename) { - if (load_image(initrd_filename, - phys_ram_base + INITRD_LOAD_ADDR + VIRT_TO_PHYS_ADDEND) - =3D=3D (target_ulong) -1) { - fprintf(stderr, "qemu: could not load initial ram disk '%s= '\n",=20 - initrd_filename); - exit(1); - } - } - - /* Store command line. */ - strcpy (phys_ram_base + (16 << 20) - 256, kernel_cmdline); - /* FIXME: little endian support */ - *(int *)(phys_ram_base + (16 << 20) - 260) =3D tswap32 (0x12345678= ); - *(int *)(phys_ram_base + (16 << 20) - 264) =3D tswap32 (ram_size); + load_kernel (env, ram_size, kernel_filename, kernel_cmdline, + initrd_filename); + env->ram_size =3D ram_size; + env->kernel_filename =3D kernel_filename; + env->kernel_cmdline =3D kernel_cmdline; + env->initrd_filename =3D initrd_filename; } =20 /* Init internal devices */ Index: qemu-work/target-mips/cpu.h =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- qemu-work.orig/target-mips/cpu.h 2006-11-15 22:41:04.000000000 +0000 +++ qemu-work/target-mips/cpu.h 2006-11-15 22:41:14.000000000 +0000 @@ -182,7 +182,6 @@ uint32_t CP0_ErrorEPC; uint32_t CP0_DESAVE; /* Qemu */ - struct QEMUTimer *timer; /* Internal timer */ int interrupt_request; jmp_buf jmp_env; int exception_index; @@ -213,6 +212,13 @@ int halted; /* TRUE if the CPU is in suspend state */ =20 CPU_COMMON + + int ram_size; + const char *kernel_filename; + const char *kernel_cmdline; + const char *initrd_filename; + + struct QEMUTimer *timer; /* Internal timer */ }; =20 #include "cpu-all.h" Index: qemu-work/target-mips/translate.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- qemu-work.orig/target-mips/translate.c 2006-11-15 22:41:04.000000000 +0= 000 +++ qemu-work/target-mips/translate.c 2006-11-15 22:41:14.000000000 +0000 @@ -2425,7 +2425,16 @@ if (!env) return NULL; cpu_exec_init(env); + cpu_reset(env); + return env; +} + +void cpu_reset (CPUMIPSState *env) +{ + memset(env, 0, offsetof(CPUMIPSState, breakpoints)); + tlb_flush(env, 1); + /* Minimal init */ env->PC =3D 0xBFC00000; #if defined (MIPS_USES_R4K_TLB) @@ -2456,5 +2465,4 @@ #ifdef MIPS_USES_FPU env->fcr0 =3D MIPS_FCR0;=09 #endif - return env; } --q9KOos5vDmpwPx9o--