/* * Cisco router simulation platform. * Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr) * * PowerPC (32-bit) generic routines. */ #include #include #include #include #include #include #include #include #include #include "rbtree.h" #include "cpu.h" #include "dynamips.h" #include "memory.h" #include "device.h" #include "ppc32_mem.h" #include "ppc32_exec.h" #include "ppc32_jit.h" /* Reset a PowerPC CPU */ int ppc32_reset(cpu_ppc_t *cpu) { cpu->ia = PPC32_ROM_START; cpu->gpr[1] = PPC32_ROM_SP; cpu->msr = PPC32_MSR_IP; /* Restart the MTS subsystem */ ppc32_mem_restart(cpu); /* Flush JIT structures */ ppc32_jit_flush(cpu,0); return(0); } /* Initialize a PowerPC processor */ int ppc32_init(cpu_ppc_t *cpu) { /* Initialize JIT operations */ jit_op_init_cpu(cpu->gen); /* Initialize idle timer */ cpu->gen->idle_max = 1500; cpu->gen->idle_sleep_time = 30000; /* Timer IRQ parameters (default frequency: 250 Hz <=> 4ms period) */ cpu->timer_irq_check_itv = 1000; cpu->timer_irq_freq = 250; /* Enable/disable direct block jump */ cpu->exec_blk_direct_jump = cpu->vm->exec_blk_direct_jump; /* Idle loop mutex and condition */ pthread_mutex_init(&cpu->gen->idle_mutex,NULL); pthread_cond_init(&cpu->gen->idle_cond,NULL); /* Set the CPU methods */ cpu->gen->reg_set = (void *)ppc32_reg_set; cpu->gen->reg_dump = (void *)ppc32_dump_regs; cpu->gen->mmu_dump = (void *)ppc32_dump_mmu; cpu->gen->mmu_raw_dump = (void *)ppc32_dump_mmu; cpu->gen->add_breakpoint = (void *)ppc32_add_breakpoint; cpu->gen->remove_breakpoint = (void *)ppc32_remove_breakpoint; cpu->gen->set_idle_pc = (void *)ppc32_set_idle_pc; cpu->gen->get_idling_pc = (void *)ppc32_get_idling_pc; /* Set the startup parameters */ ppc32_reset(cpu); return(0); } /* Delete a PowerPC processor */ void ppc32_delete(cpu_ppc_t *cpu) { if (cpu) { ppc32_mem_shutdown(cpu); ppc32_jit_shutdown(cpu); } } /* Set the processor version register (PVR) */ void ppc32_set_pvr(cpu_ppc_t *cpu,m_uint32_t pvr) { cpu->pvr = pvr; ppc32_mem_restart(cpu); } /* Set idle PC value */ void ppc32_set_idle_pc(cpu_gen_t *cpu,m_uint64_t addr) { CPU_PPC32(cpu)->idle_pc = (m_uint32_t)addr; } /* Timer IRQ */ void *ppc32_timer_irq_run(cpu_ppc_t *cpu) { pthread_mutex_t umutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t ucond = PTHREAD_COND_INITIALIZER; struct timespec t_spc; m_tmcnt_t expire; u_int interval; u_int threshold; #if 0 while(!cpu->timer_irq_armed) sleep(1); #endif interval = 1000000 / cpu->timer_irq_freq; threshold = cpu->timer_irq_freq * 10; expire = m_gettime_usec() + interval; while(cpu->gen->state != CPU_STATE_HALTED) { pthread_mutex_lock(&umutex); t_spc.tv_sec = expire / 1000000; t_spc.tv_nsec = (expire % 1000000) * 1000; pthread_cond_timedwait(&ucond,&umutex,&t_spc); pthread_mutex_unlock(&umutex); if (likely(!cpu->irq_disable) && likely(cpu->gen->state == CPU_STATE_RUNNING) && likely(cpu->msr & PPC32_MSR_EE)) { cpu->timer_irq_pending++; if (unlikely(cpu->timer_irq_pending > threshold)) { cpu->timer_irq_pending = 0; cpu->timer_drift++; #if 0 printf("Timer IRQ not accurate (%u pending IRQ): " "reduce the \"--timer-irq-check-itv\" parameter " "(current value: %u)\n", cpu->timer_irq_pending,cpu->timer_irq_check_itv); #endif } } expire += interval; } return NULL; } #define IDLE_HASH_SIZE 8192 /* Idle PC hash item */ struct ppc32_idle_pc_hash { m_uint32_t ia; u_int count; struct ppc32_idle_pc_hash *next; }; /* Determine an "idling" PC */ int ppc32_get_idling_pc(cpu_gen_t *cpu) { cpu_ppc_t *pcpu = CPU_PPC32(cpu); struct ppc32_idle_pc_hash **pc_hash,*p; struct cpu_idle_pc *res; u_int h_index,res_count; m_uint32_t cur_ia; int i; cpu->idle_pc_prop_count = 0; if (pcpu->idle_pc != 0) { printf("\nYou already use an idle PC, using the calibration would give " "incorrect results.\n"); return(-1); } printf("\nPlease wait while gathering statistics...\n"); pc_hash = calloc(IDLE_HASH_SIZE,sizeof(struct ppc32_idle_pc_hash *)); /* Disable IRQ */ pcpu->irq_disable = TRUE; /* Take 1000 measures, each mesure every 10ms */ for(i=0;i<1000;i++) { cur_ia = pcpu->ia; h_index = (cur_ia >> 2) & (IDLE_HASH_SIZE-1); for(p=pc_hash[h_index];p;p=p->next) if (p->ia == cur_ia) { p->count++; break; } if (!p) { if ((p = malloc(sizeof(*p)))) { p->ia = cur_ia; p->count = 1; p->next = pc_hash[h_index]; pc_hash[h_index] = p; } } usleep(10000); } /* Select PCs */ for(i=0,res_count=0;inext) if ((p->count >= 20) && (p->count <= 80)) { res = &cpu->idle_pc_prop[cpu->idle_pc_prop_count++]; res->pc = p->ia; res->count = p->count; if (cpu->idle_pc_prop_count >= CPU_IDLE_PC_MAX_RES) goto done; } } done: /* Set idle PC */ if (cpu->idle_pc_prop_count) { printf("Done. Suggested idling PC:\n"); for(i=0;iidle_pc_prop_count;i++) { printf(" 0x%llx (count=%u)\n", cpu->idle_pc_prop[i].pc, cpu->idle_pc_prop[i].count); } printf("Restart the emulator with \"--idle-pc=0x%llx\" (for example)\n", cpu->idle_pc_prop[0].pc); } else { printf("Done. No suggestion for idling PC, dumping the full table:\n"); for(i=0;inext) { printf(" 0x%8.8x (%3u)\n",p->ia,p->count); if (cpu->idle_pc_prop_count < CPU_IDLE_PC_MAX_RES) { res = &cpu->idle_pc_prop[cpu->idle_pc_prop_count++]; res->pc = p->ia; res->count = p->count; } } printf("\n"); } /* Re-enable IRQ */ pcpu->irq_disable = FALSE; return(0); } #if 0 /* Set an IRQ (VM IRQ standard routing) */ void ppc32_vm_set_irq(vm_instance_t *vm,u_int irq) { cpu_ppc_t *boot_cpu; boot_cpu = CPU_PPC32(vm->boot_cpu); if (boot_cpu->irq_disable) { boot_cpu->irq_pending = 0; return; } ppc32_set_irq(boot_cpu,irq); if (boot_cpu->irq_idle_preempt[irq]) cpu_idle_break_wait(vm->boot_cpu); } /* Clear an IRQ (VM IRQ standard routing) */ void ppc32_vm_clear_irq(vm_instance_t *vm,u_int irq) { cpu_ppc_t *boot_cpu; boot_cpu = CPU_PPC32(vm->boot_cpu); ppc32_clear_irq(boot_cpu,irq); } #endif /* Generate an exception */ void ppc32_trigger_exception(cpu_ppc_t *cpu,u_int exc_vector) { //printf("TRIGGER_EXCEPTION: saving cpu->ia=0x%8.8x, msr=0x%8.8x\n", // cpu->ia,cpu->msr); /* Save the return instruction address */ cpu->srr0 = cpu->ia; if (exc_vector == PPC32_EXC_SYSCALL) cpu->srr0 += sizeof(ppc_insn_t); //printf("SRR0 = 0x%8.8x\n",cpu->srr0); /* Save Machine State Register (MSR) */ cpu->srr1 = cpu->msr & PPC32_EXC_SRR1_MASK; //printf("SRR1 = 0x%8.8x\n",cpu->srr1); /* Set the new SRR value */ cpu->msr &= ~PPC32_EXC_MSR_MASK; cpu->irq_check = FALSE; //printf("MSR = 0x%8.8x\n",cpu->msr); /* Use bootstrap vectors ? */ if (cpu->msr & PPC32_MSR_IP) cpu->ia = 0xFFF00000 + exc_vector; else cpu->ia = exc_vector; } /* Trigger IRQs */ fastcall void ppc32_trigger_irq(cpu_ppc_t *cpu) { if (unlikely(cpu->irq_disable)) { cpu->irq_pending = FALSE; cpu->irq_check = FALSE; return; } /* Clear the IRQ check flag */ cpu->irq_check = FALSE; if (cpu->irq_pending && (cpu->msr & PPC32_MSR_EE)) { cpu->irq_count++; cpu->irq_pending = FALSE; ppc32_trigger_exception(cpu,PPC32_EXC_EXT); } } /* Trigger the decrementer exception */ void ppc32_trigger_timer_irq(cpu_ppc_t *cpu) { cpu->timer_irq_count++; if (cpu->msr & PPC32_MSR_EE) ppc32_trigger_exception(cpu,PPC32_EXC_DEC); } /* Virtual breakpoint */ fastcall void ppc32_run_breakpoint(cpu_ppc_t *cpu) { cpu_log(cpu->gen,"BREAKPOINT", "Virtual breakpoint reached at IA=0x%8.8x\n",cpu->ia); printf("[[[ Virtual Breakpoint reached at IA=0x%8.8x LR=0x%8.8x]]]\n", cpu->ia,cpu->lr); ppc32_dump_regs(cpu->gen); } /* Add a virtual breakpoint */ int ppc32_add_breakpoint(cpu_gen_t *cpu,m_uint64_t ia) { cpu_ppc_t *pcpu = CPU_PPC32(cpu); int i; for(i=0;ibreakpoints[i]) break; if (i == PPC32_MAX_BREAKPOINTS) return(-1); pcpu->breakpoints[i] = ia; pcpu->breakpoints_enabled = TRUE; return(0); } /* Remove a virtual breakpoint */ void ppc32_remove_breakpoint(cpu_gen_t *cpu,m_uint64_t ia) { cpu_ppc_t *pcpu = CPU_PPC32(cpu); int i,j; for(i=0;ibreakpoints[i] == ia) { for(j=i;jbreakpoints[j] = pcpu->breakpoints[j+1]; pcpu->breakpoints[PPC32_MAX_BREAKPOINTS-1] = 0; } for(i=0;ibreakpoints[i] != 0) return; pcpu->breakpoints_enabled = FALSE; } /* Set a register */ void ppc32_reg_set(cpu_gen_t *cpu,u_int reg,m_uint64_t val) { if (reg < PPC32_GPR_NR) CPU_PPC32(cpu)->gpr[reg] = (m_uint32_t)val; } /* Dump registers of a PowerPC processor */ void ppc32_dump_regs(cpu_gen_t *cpu) { cpu_ppc_t *pcpu = CPU_PPC32(cpu); int i; printf("PowerPC Registers:\n"); for(i=0;igpr[i*4], (i*4)+1, pcpu->gpr[(i*4)+1], (i*4)+2, pcpu->gpr[(i*4)+2], (i*4)+3, pcpu->gpr[(i*4)+3]); } printf("\n"); printf(" ia = 0x%8.8x, lr = 0x%8.8x\n", pcpu->ia, pcpu->lr); printf(" cr = 0x%8.8x, msr = 0x%8.8x, xer = 0x%8.8x, dec = 0x%8.8x\n", ppc32_get_cr(pcpu), pcpu->msr, pcpu->xer | (pcpu->xer_ca << PPC32_XER_CA_BIT), pcpu->dec); printf(" sprg[0] = 0x%8.8x, sprg[1] = 0x%8.8x\n", pcpu->sprg[0],pcpu->sprg[1]); printf(" sprg[2] = 0x%8.8x, sprg[3] = 0x%8.8x\n", pcpu->sprg[2],pcpu->sprg[3]); printf("\n IRQ count: %llu, IRQ false positives: %llu, " "IRQ Pending: %u, IRQ Check: %s\n", pcpu->irq_count,pcpu->irq_fp_count,pcpu->irq_pending, pcpu->irq_check ? "yes" : "no"); printf(" Timer IRQ count: %llu, pending: %u, timer drift: %u\n\n", pcpu->timer_irq_count,pcpu->timer_irq_pending,pcpu->timer_drift); printf(" Device access count: %llu\n",cpu->dev_access_counter); printf("\n"); } /* Dump BAT registers */ static void ppc32_dump_bat(cpu_ppc_t *cpu,int index) { int i; for(i=0;ibat[index][i].reg[0],cpu->bat[index][i].reg[1]); } /* Dump MMU registers */ void ppc32_dump_mmu(cpu_gen_t *cpu) { cpu_ppc_t *pcpu = CPU_PPC32(cpu); int i; printf("PowerPC MMU Registers:\n"); printf(" - IBAT Registers:\n"); ppc32_dump_bat(pcpu,PPC32_IBAT_IDX); printf(" - DBAT Registers:\n"); ppc32_dump_bat(pcpu,PPC32_DBAT_IDX); printf(" - Segment Registers:\n"); for(i=0;isr[i]); printf(" - SDR1: 0x%8.8x\n",pcpu->sdr1); } /* Load a raw image into the simulated memory */ int ppc32_load_raw_image(cpu_ppc_t *cpu,char *filename,m_uint32_t vaddr) { struct stat file_info; size_t len,clen; m_uint32_t remain; void *haddr; FILE *bfd; if (!(bfd = fopen(filename,"r"))) { perror("fopen"); return(-1); } if (fstat(fileno(bfd),&file_info) == -1) { perror("stat"); return(-1); } len = file_info.st_size; printf("Loading RAW file '%s' at virtual address 0x%8.8x (size=%lu)\n", filename,vaddr,(u_long)len); while(len > 0) { haddr = cpu->mem_op_lookup(cpu,vaddr,PPC32_MTS_DCACHE); if (!haddr) { fprintf(stderr,"load_raw_image: invalid load address 0x%8.8x\n", vaddr); return(-1); } if (len > PPC32_MIN_PAGE_SIZE) clen = PPC32_MIN_PAGE_SIZE; else clen = len; remain = MIPS_MIN_PAGE_SIZE; remain -= (vaddr - (vaddr & MIPS_MIN_PAGE_MASK)); clen = m_min(clen,remain); if (fread((u_char *)haddr,clen,1,bfd) != 1) break; vaddr += clen; len -= clen; } fclose(bfd); return(0); } /* Load an ELF image into the simulated memory */ int ppc32_load_elf_image(cpu_ppc_t *cpu,char *filename,int skip_load, m_uint32_t *entry_point) { m_uint32_t vaddr,remain; void *haddr; Elf32_Ehdr *ehdr; Elf32_Shdr *shdr; Elf_Scn *scn; Elf *img_elf; size_t len,clen; char *name; int i,fd; FILE *bfd; if (!filename) return(-1); #ifdef __CYGWIN__ fd = open(filename,O_RDONLY|O_BINARY); #else fd = open(filename,O_RDONLY); #endif if (fd == -1) { perror("load_elf_image: open"); return(-1); } if (elf_version(EV_CURRENT) == EV_NONE) { fprintf(stderr,"load_elf_image: library out of date\n"); return(-1); } if (!(img_elf = elf_begin(fd,ELF_C_READ,NULL))) { fprintf(stderr,"load_elf_image: elf_begin: %s\n", elf_errmsg(elf_errno())); return(-1); } if (!(ehdr = elf32_getehdr(img_elf))) { fprintf(stderr,"load_elf_image: invalid ELF file\n"); return(-1); } printf("Loading ELF file '%s'...\n",filename); bfd = fdopen(fd,"rb"); if (!bfd) { perror("load_elf_image: fdopen"); return(-1); } if (!skip_load) { for(i=0;ie_shnum;i++) { scn = elf_getscn(img_elf,i); shdr = elf32_getshdr(scn); name = elf_strptr(img_elf, ehdr->e_shstrndx, (size_t)shdr->sh_name); len = shdr->sh_size; if (!(shdr->sh_flags & SHF_ALLOC) || !len) continue; fseek(bfd,shdr->sh_offset,SEEK_SET); vaddr = shdr->sh_addr; if (cpu->vm->debug_level > 0) { printf(" * Adding section at virtual address 0x%8.8x " "(len=0x%8.8lx)\n",vaddr,(u_long)len); } while(len > 0) { haddr = cpu->mem_op_lookup(cpu,vaddr,PPC32_MTS_DCACHE); if (!haddr) { fprintf(stderr,"load_elf_image: invalid load address 0x%x\n", vaddr); return(-1); } if (len > PPC32_MIN_PAGE_SIZE) clen = PPC32_MIN_PAGE_SIZE; else clen = len; remain = PPC32_MIN_PAGE_SIZE; remain -= (vaddr - (vaddr & PPC32_MIN_PAGE_MASK)); clen = m_min(clen,remain); if (fread((u_char *)haddr,clen,1,bfd) < 1) break; vaddr += clen; len -= clen; } } } else { printf("ELF loading skipped, using a ghost RAM file.\n"); } printf("ELF entry point: 0x%x\n",ehdr->e_entry); if (entry_point) *entry_point = ehdr->e_entry; elf_end(img_elf); fclose(bfd); return(0); }