/* * Cisco router simulation platform. * Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr) * * Virtual machine abstraction. */ #include #include #include #include #include #include #include #include #include "registry.h" #include "device.h" #include "pci_dev.h" #include "pci_io.h" #include "cpu.h" #include "mips64_jit.h" #include "vm.h" #include "dev_vtty.h" #include MIPS64_ARCH_INC_FILE #define DEBUG_VM 1 #define VM_GLOCK() pthread_mutex_lock(&vm_global_lock) #define VM_GUNLOCK() pthread_mutex_unlock(&vm_global_lock) /* Type of VM file naming (0=use VM name, 1=use instance ID) */ int vm_file_naming_type = 0; /* Pool of ghost images */ static vm_ghost_image_t *vm_ghost_pool = NULL; /* Global lock for VM manipulation */ static pthread_mutex_t vm_global_lock = PTHREAD_MUTEX_INITIALIZER; /* Free all chunks used by a VM */ static void vm_chunk_free_all(vm_instance_t *vm); /* Initialize a VM object */ void vm_object_init(vm_obj_t *obj) { memset(obj,0,sizeof(*obj)); } /* Add a VM object to an instance */ void vm_object_add(vm_instance_t *vm,vm_obj_t *obj) { obj->next = vm->vm_object_list; obj->pprev = &vm->vm_object_list; if (vm->vm_object_list) vm->vm_object_list->pprev = &obj->next; vm->vm_object_list = obj; } /* Remove a VM object from an instance */ void vm_object_remove(vm_instance_t *vm,vm_obj_t *obj) { if (obj->next) obj->next->pprev = obj->pprev; *(obj->pprev) = obj->next; obj->shutdown(vm,obj->data); } /* Find an object given its name */ vm_obj_t *vm_object_find(vm_instance_t *vm,char *name) { vm_obj_t *obj; for(obj=vm->vm_object_list;obj;obj=obj->next) if (!strcmp(obj->name,name)) return obj; return NULL; } /* Check that a mandatory object is present */ int vm_object_check(vm_instance_t *vm,char *name) { return(vm_object_find(vm,name) ? 0 : -1); } /* Shut down all objects of an instance */ void vm_object_free_list(vm_instance_t *vm) { vm_obj_t *obj,*next; for(obj=vm->vm_object_list;obj;obj=next) { next = obj->next; if (obj->shutdown != NULL) { #if DEBUG_VM vm_log(vm,"VM_OBJECT","Shutdown of object \"%s\"\n",obj->name); #endif obj->shutdown(vm,obj->data); } } vm->vm_object_list = NULL; } /* Rebuild the object list pointers */ static void vm_object_rebuild_list(vm_instance_t *vm) { vm_obj_t **obj; for(obj=&vm->vm_object_list;*obj;obj=&(*obj)->next) (*obj)->pprev = obj; } /* Dump the object list of an instance */ void vm_object_dump(vm_instance_t *vm) { vm_obj_t *obj; printf("VM \"%s\" (%u) object list:\n",vm->name,vm->instance_id); for(obj=vm->vm_object_list;obj;obj=obj->next) { printf(" - %-15s [data=%p]\n",obj->name,obj->data); } printf("\n"); } /* Get VM type */ char *vm_get_type(vm_instance_t *vm) { char *machine; switch(vm->type) { case VM_TYPE_C3600: machine = "c3600"; break; case VM_TYPE_C7200: machine = "c7200"; break; case VM_TYPE_C2691: machine = "c2691"; break; case VM_TYPE_C3725: machine = "c3725"; break; case VM_TYPE_C3745: machine = "c3745"; break; case VM_TYPE_C2600: machine = "c2600"; break; case VM_TYPE_MSFC1: machine = "msfc1"; break; case VM_TYPE_PPC32_TEST: machine = "ppc32_test"; break; default: machine = "unknown"; break; } return machine; } /* Get platform type */ char *vm_get_platform_type(vm_instance_t *vm) { char *machine; switch(vm->type) { case VM_TYPE_C3600: machine = "C3600"; break; case VM_TYPE_C7200: machine = "C7200"; break; case VM_TYPE_C2691: machine = "C2691"; break; case VM_TYPE_C3725: machine = "C3725"; break; case VM_TYPE_C3745: machine = "C3745"; break; case VM_TYPE_C2600: machine = "C2600"; break; case VM_TYPE_MSFC1: machine = "MSFC1"; break; case VM_TYPE_PPC32_TEST: machine = "PPC32_TEST"; break; default: machine = "VM"; break; } return machine; } /* Get MAC address MSB */ u_int vm_get_mac_addr_msb(vm_instance_t *vm) { switch(vm->type) { case VM_TYPE_C3600: return(0xCC); case VM_TYPE_C7200: return(0xCA); case VM_TYPE_C2691: return(0xC0); case VM_TYPE_C3725: return(0xC2); case VM_TYPE_C3745: return(0xC4); case VM_TYPE_C2600: return(0xC8); default: return(0xC6); } } /* Generate a filename for use by the instance */ char *vm_build_filename(vm_instance_t *vm,char *name) { char *filename,*machine; machine = vm_get_type(vm); switch(vm_file_naming_type) { case 1: filename = dyn_sprintf("%s_i%u_%s",machine,vm->instance_id,name); break; case 0: default: filename = dyn_sprintf("%s_%s_%s",machine,vm->name,name); break; } assert(filename != NULL); return filename; } /* Erase lock file */ void vm_release_lock(vm_instance_t *vm,int erase) { if (vm->lock_fd != NULL) { fclose(vm->lock_fd); vm->lock_fd = NULL; } if (vm->lock_file != NULL) { if (erase) unlink(vm->lock_file); free(vm->lock_file); vm->lock_file = NULL; } } /* Check that an instance lock file doesn't already exist */ int vm_get_lock(vm_instance_t *vm) { char pid_str[32]; struct flock lock; vm->lock_file = vm_build_filename(vm,"lock"); if (!(vm->lock_fd = fopen(vm->lock_file,"w"))) { fprintf(stderr,"Unable to create lock file \"%s\".\n",vm->lock_file); return(-1); } memset(&lock,0,sizeof(lock)); lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(fileno(vm->lock_fd),F_SETLK,&lock) == -1) { if (fcntl(fileno(vm->lock_fd),F_GETLK,&lock) == 0) { snprintf(pid_str,sizeof(pid_str),"%ld",(long)lock.l_pid); } else { strcpy(pid_str,"unknown"); } fprintf(stderr, "\nAn emulator instance (PID %s) is already running with " "identifier %u.\n" "If this is not the case, please erase file \"%s\".\n\n", pid_str,vm->instance_id,vm->lock_file); vm_release_lock(vm,FALSE); return(-1); } /* write the emulator PID */ fprintf(vm->lock_fd,"%ld\n",(u_long)getpid()); return(0); } /* Log a message */ void vm_flog(vm_instance_t *vm,char *module,char *format,va_list ap) { if (vm->log_fd) m_flog(vm->log_fd,module,format,ap); } /* Log a message */ void vm_log(vm_instance_t *vm,char *module,char *format,...) { va_list ap; if (vm->log_fd) { va_start(ap,format); vm_flog(vm,module,format,ap); va_end(ap); } } /* Close the log file */ int vm_close_log(vm_instance_t *vm) { if (vm->log_fd) fclose(vm->log_fd); free(vm->log_file); vm->log_file = NULL; vm->log_fd = NULL; return(0); } /* Create the log file */ int vm_create_log(vm_instance_t *vm) { if (vm->log_file_enabled) { vm_close_log(vm); if (!(vm->log_file = vm_build_filename(vm,"log.txt"))) return(-1); if (!(vm->log_fd = fopen(vm->log_file,"w"))) { fprintf(stderr,"VM %s: unable to create log file '%s'\n", vm->name,vm->log_file); free(vm->log_file); vm->log_file = NULL; return(-1); } } return(0); } /* Error message */ void vm_error(vm_instance_t *vm,char *format,...) { char buffer[2048]; va_list ap; va_start(ap,format); vsnprintf(buffer,sizeof(buffer),format,ap); va_end(ap); fprintf(stderr,"%s '%s': %s",vm_get_platform_type(vm),vm->name,buffer); } /* Create a new VM instance */ vm_instance_t *vm_create(char *name,int instance_id,int machine_type) { vm_instance_t *vm; if (!(vm = malloc(sizeof(*vm)))) { fprintf(stderr,"VM %s: unable to create new instance!\n",name); return NULL; } memset(vm,0,sizeof(*vm)); vm->instance_id = instance_id; vm->type = machine_type; vm->status = VM_STATUS_HALTED; vm->jit_use = JIT_SUPPORT; vm->exec_blk_direct_jump = TRUE; vm->vtty_con_type = VTTY_TYPE_TERM; vm->vtty_aux_type = VTTY_TYPE_NONE; vm->timer_irq_check_itv = VM_TIMER_IRQ_CHECK_ITV; vm->log_file_enabled = TRUE; if (!(vm->name = strdup(name))) { fprintf(stderr,"VM %s: unable to store instance name!\n",name); goto err_name; } /* create lock file */ if (vm_get_lock(vm) == -1) goto err_lock; /* create log file */ if (vm_create_log(vm) == -1) goto err_log; if (registry_add(vm->name,OBJ_TYPE_VM,vm) == -1) { fprintf(stderr,"VM: Unable to store instance '%s' in registry!\n", vm->name); goto err_reg_add; } return vm; err_reg_add: vm_close_log(vm); err_log: free(vm->lock_file); err_lock: free(vm->name); err_name: free(vm); return NULL; } /* * Shutdown hardware resources used by a VM. * The CPU must have been stopped. */ int vm_hardware_shutdown(vm_instance_t *vm) { int i; if ((vm->status == VM_STATUS_HALTED) || !vm->cpu_group) { vm_log(vm,"VM","trying to shutdown an inactive VM.\n"); return(-1); } vm_log(vm,"VM","shutdown procedure engaged.\n"); /* Mark the VM as halted */ vm->status = VM_STATUS_HALTED; /* Disable NVRAM operations */ vm->nvram_extract_config = NULL; vm->nvram_push_config = NULL; /* Free the object list */ vm_object_free_list(vm); /* Free resources used by PCI busses */ vm_log(vm,"VM","removing PCI busses.\n"); pci_io_data_remove(vm,vm->pci_io_space); pci_bus_remove(vm->pci_bus[0]); pci_bus_remove(vm->pci_bus[1]); vm->pci_bus[0] = vm->pci_bus[1] = NULL; /* Free the PCI bus pool */ for(i=0;ipci_bus_pool[i] != NULL) { pci_bus_remove(vm->pci_bus_pool[i]); vm->pci_bus_pool[i] = NULL; } } /* Remove the IRQ routing vectors */ vm->set_irq = NULL; vm->clear_irq = NULL; /* Delete the VTTY for Console and AUX ports */ vm_log(vm,"VM","deleting VTTY.\n"); vm_delete_vtty(vm); /* Delete system CPU group */ vm_log(vm,"VM","deleting system CPUs.\n"); cpu_group_delete(vm->cpu_group); vm->cpu_group = NULL; vm->boot_cpu = NULL; vm_log(vm,"VM","shutdown procedure completed.\n"); return(0); } /* Free resources used by a VM */ void vm_free(vm_instance_t *vm) { if (vm != NULL) { /* Free hardware resources */ vm_hardware_shutdown(vm); /* Close log file */ vm_close_log(vm); /* Remove the lock file */ vm_release_lock(vm,TRUE); /* Free all chunks */ vm_chunk_free_all(vm); /* Free various elements */ free(vm->ghost_ram_filename); free(vm->sym_filename); free(vm->ios_image); free(vm->ios_config); free(vm->rom_filename); free(vm->name); free(vm); } } /* Get an instance given a name */ vm_instance_t *vm_acquire(char *name) { return(registry_find(name,OBJ_TYPE_VM)); } /* Release a VM (decrement reference count) */ int vm_release(vm_instance_t *vm) { return(registry_unref(vm->name,OBJ_TYPE_VM)); } /* Initialize RAM */ int vm_ram_init(vm_instance_t *vm,m_uint64_t paddr) { m_uint32_t len; len = vm->ram_size * 1048576; if (vm->ghost_status == VM_GHOST_RAM_USE) { return(dev_ram_ghost_init(vm,"ram",vm->sparse_mem,vm->ghost_ram_filename, paddr,len)); } return(dev_ram_init(vm,"ram",vm->ram_mmap, (vm->ghost_status != VM_GHOST_RAM_GENERATE), vm->ghost_ram_filename,vm->sparse_mem,paddr,len)); } /* Initialize VTTY */ int vm_init_vtty(vm_instance_t *vm) { /* Create Console and AUX ports */ vm->vtty_con = vtty_create(vm,"Console port", vm->vtty_con_type,vm->vtty_con_tcp_port, &vm->vtty_con_serial_option); vm->vtty_aux = vtty_create(vm,"AUX port", vm->vtty_aux_type,vm->vtty_aux_tcp_port, &vm->vtty_aux_serial_option); return(0); } /* Delete VTTY */ void vm_delete_vtty(vm_instance_t *vm) { vtty_delete(vm->vtty_con); vtty_delete(vm->vtty_aux); vm->vtty_con = vm->vtty_aux = NULL; } /* Bind a device to a virtual machine */ int vm_bind_device(vm_instance_t *vm,struct vdevice *dev) { struct vdevice **cur; u_int i; /* * Add this device to the device array. The index in the device array * is used by the MTS subsystem. */ for(i=0;idev_array[i]) break; if (i == VM_DEVICE_MAX) { fprintf(stderr,"VM%u: vm_bind_device: device table full.\n", vm->instance_id); return(-1); } vm->dev_array[i] = dev; dev->id = i; /* * Add it to the linked-list (devices are ordered by physical addresses). */ for(cur=&vm->dev_list;*cur;cur=&(*cur)->next) if ((*cur)->phys_addr > dev->phys_addr) break; dev->next = *cur; if (*cur) (*cur)->pprev = &dev->next; dev->pprev = cur; *cur = dev; return(0); } /* Unbind a device from a virtual machine */ int vm_unbind_device(vm_instance_t *vm,struct vdevice *dev) { u_int i; if (!dev || !dev->pprev) return(-1); /* Remove the device from the linked list */ if (dev->next) dev->next->pprev = dev->pprev; *(dev->pprev) = dev->next; /* Remove the device from the device array */ for(i=0;idev_array[i] == dev) { vm->dev_array[i] = NULL; break; } /* Clear device list info */ dev->next = NULL; dev->pprev = NULL; return(0); } /* Map a device at the specified physical address */ int vm_map_device(vm_instance_t *vm,struct vdevice *dev,m_uint64_t base_addr) { #if 0 /* Suspend VM activity */ vm_suspend(vm); if (cpu_group_sync_state(vm->cpu_group) == -1) { fprintf(stderr,"VM%u: unable to sync with system CPUs.\n", vm->instance_id); return(-1); } #endif /* Unbind the device if it was already active */ vm_unbind_device(vm,dev); /* Map the device at the new base address and rebuild MTS */ dev->phys_addr = base_addr; vm_bind_device(vm,dev); cpu_group_rebuild_mts(vm->cpu_group); #if 0 vm_resume(vm); #endif return(0); } /* Suspend a VM instance */ int vm_suspend(vm_instance_t *vm) { if (vm->status == VM_STATUS_RUNNING) { cpu_group_save_state(vm->cpu_group); cpu_group_set_state(vm->cpu_group,CPU_STATE_SUSPENDED); vm->status = VM_STATUS_SUSPENDED; } return(0); } /* Resume a VM instance */ int vm_resume(vm_instance_t *vm) { if (vm->status == VM_STATUS_SUSPENDED) { cpu_group_restore_state(vm->cpu_group); vm->status = VM_STATUS_RUNNING; } return(0); } /* Stop an instance */ int vm_stop(vm_instance_t *vm) { cpu_group_stop_all_cpu(vm->cpu_group); vm->status = VM_STATUS_SHUTDOWN; return(0); } /* Monitor an instance periodically */ void vm_monitor(vm_instance_t *vm) { while(vm->status != VM_STATUS_SHUTDOWN) usleep(200000); } /* Create a new chunk */ static vm_chunk_t *vm_chunk_create(vm_instance_t *vm) { vm_chunk_t *chunk; size_t area_len; if (!(chunk = malloc(sizeof(*chunk)))) return NULL; area_len = VM_CHUNK_AREA_SIZE * VM_PAGE_SIZE; if (!(chunk->area = m_memalign(VM_PAGE_SIZE,area_len))) { free(chunk); return NULL; } chunk->page_alloc = 0; chunk->page_total = VM_CHUNK_AREA_SIZE; chunk->next = vm->chunks; vm->chunks = chunk; return chunk; } /* Free a chunk */ static void vm_chunk_free(vm_chunk_t *chunk) { free(chunk->area); free(chunk); } /* Free all chunks used by a VM */ static void vm_chunk_free_all(vm_instance_t *vm) { vm_chunk_t *chunk,*next; for(chunk=vm->chunks;chunk;chunk=next) { next = chunk->next; vm_chunk_free(chunk); } vm->chunks = NULL; } /* Allocate an host page */ void *vm_alloc_host_page(vm_instance_t *vm) { vm_chunk_t *chunk = vm->chunks; void *ptr; if (!chunk || (chunk->page_alloc == chunk->page_total)) { chunk = vm_chunk_create(vm); if (!chunk) return NULL; } ptr = chunk->area + (chunk->page_alloc * VM_PAGE_SIZE); chunk->page_alloc++; return(ptr); } /* Free resources used by a ghost image */ static void vm_ghost_image_free(vm_ghost_image_t *img) { if (img) { if (img->fd != -1) { close(img->fd); if (img->area_ptr != NULL) munmap(img->area_ptr,img->file_size); } free(img->filename); free(img); } } /* Find a specified ghost image in the pool */ static vm_ghost_image_t *vm_ghost_image_find(char *filename) { vm_ghost_image_t *img; for(img=vm_ghost_pool;img;img=img->next) if (!strcmp(img->filename,filename)) return img; return NULL; } /* Load a new ghost image */ static vm_ghost_image_t *vm_ghost_image_load(char *filename) { vm_ghost_image_t *img; if (!(img = calloc(1,sizeof(*img)))) return NULL; img->fd = -1; if (!(img->filename = strdup(filename))) { vm_ghost_image_free(img); return NULL; } img->fd = memzone_open_file(img->filename,&img->area_ptr,&img->file_size); if (img->fd == -1) { vm_ghost_image_free(img); return NULL; } m_log("GHOST","loaded ghost image %s (fd=%d) at addr=%p (size=0x%llx)\n", img->filename,img->fd,img->area_ptr,(long long)img->file_size); return img; } /* Get a ghost image */ int vm_ghost_image_get(char *filename,u_char **ptr,int *fd) { vm_ghost_image_t *img; VM_GLOCK(); /* Do we already have this image in the pool ? */ if ((img = vm_ghost_image_find(filename)) != NULL) { img->ref_count++; *ptr = img->area_ptr; *fd = img->fd; VM_GUNLOCK(); return(0); } /* Load the ghost file and add it into the pool */ if (!(img = vm_ghost_image_load(filename))) { VM_GUNLOCK(); fprintf(stderr,"Unable to load ghost image %s\n",filename); return(-1); } img->ref_count = 1; *ptr = img->area_ptr; *fd = img->fd; img->next = vm_ghost_pool; vm_ghost_pool = img; VM_GUNLOCK(); return(0); } /* Release a ghost image */ int vm_ghost_image_release(int fd) { vm_ghost_image_t **img,*next; VM_GLOCK(); for(img=&vm_ghost_pool;*img;img=&(*img)->next) { if ((*img)->fd == fd) { assert((*img)->ref_count > 0); (*img)->ref_count--; if ((*img)->ref_count == 0) { m_log("GHOST","unloaded ghost image %s (fd=%d) at " "addr=%p (size=0x%llx)\n", (*img)->filename,(*img)->fd,(*img)->area_ptr, (long long)(*img)->file_size); next = (*img)->next; vm_ghost_image_free(*img); *img = next; } VM_GUNLOCK(); return(0); } } VM_GUNLOCK(); return(-1); } /* Open a VM file and map it in memory */ int vm_mmap_open_file(vm_instance_t *vm,char *name, u_char **ptr,off_t *fsize) { char *filename; int fd; if (!(filename = vm_build_filename(vm,name))) { fprintf(stderr,"vm_mmap_open_file: unable to create filename (%s)\n", name); return(-1); } if ((fd = memzone_open_file(filename,ptr,fsize)) == -1) fprintf(stderr,"vm_mmap_open_file: unable to open file '%s' (%s)\n", filename,strerror(errno)); free(filename); return(fd); } /* Open/Create a VM file and map it in memory */ int vm_mmap_create_file(vm_instance_t *vm,char *name,size_t len,u_char **ptr) { char *filename; int fd; if (!(filename = vm_build_filename(vm,name))) { fprintf(stderr,"vm_mmap_create_file: unable to create filename (%s)\n", name); return(-1); } if ((fd = memzone_create_file(filename,len,ptr)) == -1) fprintf(stderr,"vm_mmap_create_file: unable to open file '%s' (%s)\n", filename,strerror(errno)); free(filename); return(fd); } /* Close a memory mapped file */ int vm_mmap_close_file(int fd,u_char *ptr,size_t len) { if (ptr != NULL) munmap(ptr,len); if (fd != -1) close(fd); return(0); } /* Save the Cisco IOS configuration from NVRAM */ int vm_ios_save_config(vm_instance_t *vm) { char *output; int res; if (!(output = vm_build_filename(vm,"ios_cfg.txt"))) return(-1); res = vm_nvram_extract_config(vm,output); free(output); return(res); } /* Set Cisco IOS image to use */ int vm_ios_set_image(vm_instance_t *vm,char *ios_image) { char *str; if (!(str = strdup(ios_image))) return(-1); if (vm->ios_image != NULL) { free(vm->ios_image); vm->ios_image = NULL; } vm->ios_image = str; return(0); } /* Unset a Cisco IOS configuration file */ void vm_ios_unset_config(vm_instance_t *vm) { if (vm->ios_config != NULL) { free(vm->ios_config); vm->ios_config = NULL; } } /* Set Cisco IOS configuration file to use */ int vm_ios_set_config(vm_instance_t *vm,char *ios_config) { char *str; if (!(str = strdup(ios_config))) return(-1); vm_ios_unset_config(vm); vm->ios_config = str; return(0); } /* Extract IOS configuration from NVRAM and write it to a file */ int vm_nvram_extract_config(vm_instance_t *vm,char *filename) { char *cfg_buffer; ssize_t cfg_len; FILE *fd; if (!vm->nvram_extract_config) return(-1); /* Extract the IOS configuration */ if (((cfg_len = vm->nvram_extract_config(vm,&cfg_buffer)) < 0) || (cfg_buffer == NULL)) return(-1); /* Write configuration to the specified filename */ if (!(fd = fopen(filename,"w"))) { vm_error(vm,"unable to create file '%s'\n",filename); free(cfg_buffer); return(-1); } fwrite(cfg_buffer,cfg_len,1,fd); fclose(fd); free(cfg_buffer); return(0); } /* Read an IOS configuraton from a file and push it to NVRAM */ int vm_nvram_push_config(vm_instance_t *vm,char *filename) { char *cfg_buffer; ssize_t len; int res; if (!vm->nvram_push_config) return(-1); /* Read configuration */ if (((len = m_read_file(filename,&cfg_buffer)) <= 0) || !cfg_buffer) return(-1); /* Push it! */ res = vm->nvram_push_config(vm,cfg_buffer,len); free(cfg_buffer); return(res); } /* Save general VM configuration into the specified file */ void vm_save_config(vm_instance_t *vm,FILE *fd) { if (vm->ios_image) fprintf(fd,"vm set_ios %s %s\n",vm->name,vm->ios_image); fprintf(fd,"vm set_ram %s %u\n",vm->name,vm->ram_size); fprintf(fd,"vm set_nvram %s %u\n",vm->name,vm->nvram_size); fprintf(fd,"vm set_ram_mmap %s %u\n",vm->name,vm->ram_mmap); fprintf(fd,"vm set_clock_divisor %s %u\n",vm->name,vm->clock_divisor); fprintf(fd,"vm set_conf_reg %s 0x%4.4x\n",vm->name,vm->conf_reg_setup); if (vm->vtty_con_type == VTTY_TYPE_TCP) fprintf(fd,"vm set_con_tcp_port %s %d\n",vm->name,vm->vtty_con_tcp_port); if (vm->vtty_aux_type == VTTY_TYPE_TCP) fprintf(fd,"vm set_aux_tcp_port %s %d\n",vm->name,vm->vtty_aux_tcp_port); }