/* * This file is part of the Palacios Virtual Machine Monitor developed * by the V3VEE Project with funding from the United States National * Science Foundation and the Department of Energy. * * The V3VEE Project is a joint project between Northwestern University * and the University of New Mexico. You can find out more at * http://www.v3vee.org * * Copyright (c) 2009, Lei Xia * Copyright (c) 2009, Chang Seok Bae * Copyright (c) 2009, The V3VEE Project * All rights reserved. * * Author: Lei Xia * Chang Seok Bae * * This is free software. You are permitted to use, * redistribute, and modify it as specified in the file "V3VEE_LICENSE". */ /* * Virtual PCI */ #include #include #include #include #include #include // TODO: Add Debugging directives to Makefiles #ifndef DEBUG_PCI #undef PrintDebug #define PrintDebug(fmt, args...) #endif #define NUM_DEVICES 255 #define NUM_BUS 1 struct pci_bus { int bus_num; struct pci_device * device_list[NUM_DEVICES]; struct pci_bus * next; struct vm_device * vm_dev; }; struct pci_internal { uint_t num_buses; uint32_t config_address; //current value of corresponding to configure port struct pci_bus * bus_list[NUM_BUS]; }; struct port_ops_map { uint32_t port; int (*port_read)(ushort_t port, void * dst, uint_t length, struct vm_device * vdev); int (*port_write)(ushort_t port, void * src, uint_t length, struct vm_device * vdev); }; //Lei struct pci_device * get_device (struct vm_device * vmdev, uchar_t bus_no, uchar_t devfn_no) { struct pci_device * dev = NULL; struct pci_bus * bus = NULL; struct pci_internal * pci_state = NULL; if ((bus_no >= NUM_BUS) || (devfn_no >= NUM_DEVICES)) { return dev; } pci_state = (struct pci_internal *)vmdev->private_data; bus = pci_state->bus_list[bus_no]; if (bus != NULL) { dev = bus->device_list[devfn_no]; } return dev; } //Lei // TODO: Should this be static? If not it should be v3_pci_... int pci_hook_ports(struct pci_device * dev, int reg_num, int num_ports, port_read_fn * port_reads[], port_write_fn * port_writes[]) { struct pci_ioregion * ioreg = NULL; ioreg = dev->ioregion[reg_num]; if (ioreg == NULL) { PrintError("No Device registered at ioregion %d\n", reg_num); return -1; } if (ioreg->size != num_ports) { // TODO: What does this error mean? PrintError("IO registration size mismatch\n"); return -1; } ioreg->port_reads = port_reads; ioreg->port_writes = port_writes; return 0; } //Lei // TODO: should return 'int' to indicate success or error? // TODO: add error checking static inline void hook_ioregion(struct pci_device * dev, struct pci_ioregion * ioreg) { int i = 0; if (ioreg->addr == -1) { // TODO: Is this an error? return; } if (ioreg->type != PCI_ADDRESS_SPACE_IO) { // TODO: is this an error? return; } for (i = 0; i < ioreg->size; i++) { if ( (ioreg->port_reads[i]) || (ioreg->port_writes[i]) ) { v3_dev_hook_io(dev->bus->vm_dev, ioreg->addr + i, ioreg->port_reads[i], ioreg->port_writes[i]); } } // TODO: return 0; } //Chang static uint32_t vpci_read_config(struct pci_device * pdev, uchar_t offset, int len) { uint32_t val = 0; switch(len) { case 4: if (offset <= 0xfc) { val = *(uint32_t *)(&(pdev->config) + offset); break; } // TODO: Shouldn't this break unconditionally? case 2: if (offset <= 0xfe) { val = *(uint16_t *)(&(pdev->config) + offset); break; } // TODO: Shouldn't this break unconditionally? case 1: val = *(uint8_t *)(&(pdev->config) + offset); break; default: break; } return val; } //Lei // TODO: Should this return 'int'? static void vpci_write_config(struct pci_device * dev, uchar_t offset, uint32_t val, int len) { uchar_t * dev_config = NULL; dev_config = (uchar_t *)&(dev->config); dev_config += offset; // TODO: cast 'val' instead of masking it switch(len) { case 1: *dev_config = (val & 0xff); break; case 2: *(uint16_t *)dev_config = (val & 0xffff); break; case 4: *(uint32_t *)dev_config = val; break; default: PrintDebug("pci_write_config: wrong length %d\n", len); break; } } //Lei // TODO: If this is not static, it should be v3_pci_raise_irq void vpci_raise_irq(struct pci_device * pdev, void * data) { struct guest_info * vm = NULL; int irq_line = 0; vm = pdev->bus->vm_dev->vm; irq_line = pdev->config.intr_line; v3_raise_irq(vm, irq_line); } #if 0 //Chang static void pci_write_config(struct pci_device *dev, uint32_t address, uint32_t val, int len) { int can_write, i, reg_num; uint32_t addr; if(len == 4 && ((address >= 0x10 && address < 0x10 + 4 * 6) || //base address registers (address >= 0x30 && address < 0x34))) { //expansion rom base address struct pci_ioregion * ioregion; if(address >= 0x30) { reg_num = PCI_ROM_SLOT; }else { reg_num = ((address - 0x10) >>2); } ioregion = &dev->io_regions[reg_num]; if(ioregion->size == 0) {//default config addr = address; for (i=0;iconfig)+0x0e)) { case 0x00: case 0x80: switch(addr) { case 0x00: case 0x01: case 0x02: case 0x03: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0e: case 0x10 ... 0x27: case 0x3d: can_write = 0; break; default: can_write = 1; break; } break; default: case 0x01: switch(addr) { case 0x00: case 0x01: case 0x02: case 0x03: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0e: case 0x38 ... 0x3b: case 0x3d: can_write = 0; break; default: can_write = 1; break; } break; } if (can_write) { *(uint32_t *)(&(dev->config)+addr) = val; } if(++addr > 0xff) break; val >>= 8; } return; }else { if(reg_num== PCI_ROM_SLOT) { val &= (~(ioregion->size -1 )) | 1; } else { val &= ~(ioregion->size -1); val |= ioregion->type; } } //pci_update_mappings(); return; } } #endif /* -1 for dev_num means auto assign */ // TODO: Should be v3_pci_register_device struct pci_device * pci_register_device(struct pci_bus * bus, const char * name, int instance_size, int dev_num, uint32_t (*config_read)(struct pci_device * pci_dev, uchar_t addr, int len), void (*config_write)(struct pci_device * pci_dev, uchar_t addr, uint32_t val, int len)) { struct pci_device * pci_dev = NULL; int found = 0; int i = 0; if (dev_num < 0) { for (dev_num = 0; dev_num < 256; dev_num++) { if (!bus->device_list[dev_num]) { found = 1; break; } } } if (found == 0) { return NULL; } pci_dev = (struct pci_device *)V3_Malloc(sizeof(struct pci_device)); if (pci_dev == NULL) { return NULL; } pci_dev->bus = bus; pci_dev->dev_num = dev_num; pci_dev->irqline = -1; strcpy(pci_dev->name, name); if (config_read != NULL) { pci_dev->ops.config_read = config_read; } else { pci_dev->ops.config_read = &vpci_read_config; } if (config_write != NULL) { pci_dev->ops.config_write = config_write; } else { pci_dev->ops.config_write = &vpci_write_config; } pci_dev->ops.raise_irq = &vpci_raise_irq; for (i = 0; i < PCI_IO_REGIONS; i++) { pci_dev->ioregion[i] = NULL; } //config space initiate bus->device_list[dev_num] = pci_dev; return pci_dev; } //Chang static void init_fake_device(struct pci_internal * pci_state) { //maybe need table to map device, but just //bus_num=0, dev_num=0 //int i=0; struct pci_device *fake_device; //fake dev fake_device = pci_register_device(pci_state->bus_list[0], "fake ide", sizeof(struct pci_device), -1, NULL,NULL); if (!fake_device) { return; } /* intel, ide ctroller vendor id:0x8086 device id: 0x1222 */ fake_device->config.vendor_id = 0x8086; fake_device->config.device_id = 0x1222; fake_device->config.command = 0x0; fake_device->config.status = 0x0; fake_device->config.revision = 0x07; fake_device->config.class_code[0] = 0x1; fake_device->config.class_code[1] = 0x1; fake_device->config.class_code[2] = 0x1; fake_device->config.header_type = 0x0; //base address fake_device->config.BAR[0] = 0x1F0; fake_device->config.BAR[1] = 0; fake_device->config.BAR[2] = 0; fake_device->config.BAR[3] = 0; fake_device->config.BAR[4] = 0; fake_device->config.BAR[5] = 0; //fake dev end //need to register io regions pci_state->bus_list[0]->device_list[0] = fake_device; fake_device->bus = pci_state->bus_list[0]; fake_device->next = NULL; return; } // Lei /* if region_num == -1, assign automatically */ //TODO: Should be v3_pci_register... int pci_register_io_region(struct pci_device * pci_dev, int region_num, uint32_t size, int type, pci_mapioregion_fn * map_func) { int found = 0; struct pci_ioregion * region = NULL; if (region_num < 0) { for (region_num = 0; region_num < 256; region_num++) { if (pci_dev->ioregion[region_num] == NULL) { found = 1; break; } } } if (found == 0) { return -1; } if (pci_dev->ioregion[region_num] != NULL) { return -1; } region = (struct pci_ioregion *)V3_Malloc(sizeof(struct pci_ioregion)); if (region == NULL) { return -1; } region->addr = -1; region->reg_num = region_num; region->size = size; region->mapped_size = -1; region->type = type; region->map_func = map_func; region->port_reads = NULL; region->port_writes = NULL; pci_dev->ioregion[region_num] = region; return region_num; } //Chang static int vpci_addrport_read(ushort_t port, void * dst, uint_t length, struct vm_device * dev) { struct pci_internal *pci_state = (struct pci_internal *)dev->private_data; int start = 0; uchar_t * addr = NULL; int i = 0; start = port - PCI_CONFIG_ADDRESS; if ((length + start) > 4) { //cross port boundary, is memory mapped IO style return length; } addr = (uchar_t *)&(pci_state->config_address); addr += start; memcpy(dst, addr, length); //be careful, PCI is little endian PrintDebug("PCI Address: reading %d bytes from port %x: 0x", length, port); for (i = (length - 1); i >= 0; i--) { PrintDebug("%.2x", ((uchar_t*)dst)[i]); } PrintDebug("\n"); return length; } //Lei static int vpci_addrport_write(ushort_t port, void * src, uint_t length, struct vm_device * dev) { struct pci_internal * pci_state = (struct pci_internal *)dev->private_data; int start = 0; uchar_t * addr = NULL; int i = 0; start = port - PCI_CONFIG_ADDRESS; if ((length + start) > 4){ //cross port boundary, is memory mapped IO style return length; } addr = (uchar_t *)&(pci_state->config_address); addr += start; memcpy(addr, src, length); //be careful, PCI is little endian PrintDebug("PCI Address: writing %d bytes to port %x: 0x", length, port); for (i = (length - 1); i >= 0; i--) { PrintDebug("%.2x", ((uchar_t*)src)[i]); } PrintDebug("\n"); return length; } //Chang static int vpci_dataport_read(ushort_t port, void * dst, uint_t length, struct vm_device * vmdev) { /* decode address of config_address bus num = config_address[23:16] device num = config_address[15:11] func num = config_address[10:08] reg num = config_address[07:02] */ struct pci_internal * pci_state = NULL; struct pci_device * pci_dev = NULL; int bus_num = 0; int devfn = 0; int offset = 0; uint32_t address = 0; uint32_t val = 0; int i = 0; if (length > 4){ PrintDebug("Read more than 4 bytes from port 0x%x\n", (int)port); return length; } pci_state = (struct pci_internal *)vmdev->private_data; address = pci_state->config_address; offset = address & 0xff; devfn = (address >> 8) & 0xff; bus_num = (address >> 16) & 0xff; pci_dev = get_device(vmdev, bus_num, devfn); if (pci_dev == NULL) { val = 0xffffffff; } else { val = 0x0; val = pci_dev->ops.config_read(pci_dev, offset, length); } memcpy(dst, &val, length); PrintDebug("PCI Data: reading %d bytes from port %x: 0x", length, port); for (i = (length - 1); i >= 0; i--) { PrintDebug("%.2x", ((uchar_t*)dst)[i]); } PrintDebug("\n"); return length; } static int vpci_dataport_write(ushort_t port, void * src, uint_t length, struct vm_device * vmdev) { struct pci_internal * pci_state = NULL; uint32_t val = 0; uint32_t address = 0; struct pci_device * pdev = NULL; // TODO: Why are these 'char', but the read variables 'int' char bus = 0; char devfn = 0; char offset = 0; int i = 0; if (length > 4){ PrintDebug("Write more than 4 bytes to port 0x%x\n", (int)port); return length; } pci_state = (struct pci_internal *)vmdev->private_data; address = pci_state->config_address; offset = address & 0xff; devfn = (address >> 8) & 0xff; bus = (address >> 16) & 0xff; pdev = get_device(vmdev, bus, devfn); if (pdev == NULL){ // not sure what to do here, just ignore it return length; } val = 0x0; memcpy(&val, src, length); pdev->ops.config_write(pdev, offset, val, length); PrintDebug("PCI Data: writing %d bytes to port %x: 0x", length, port); for (i = (length - 1); i >= 0; i--) { PrintDebug("%.2x", ((uchar_t*)src)[i]); } PrintDebug("\n"); return length; } //Lei static void init_pci_bus(struct pci_internal * pci_state) { int i = 0; struct pci_bus * first_bus = NULL; first_bus = (struct pci_bus *)V3_Malloc(sizeof(struct pci_bus)); first_bus->bus_num = 0; //?? not sure for (i = 0; i < NUM_DEVICES; i++) { first_bus->device_list[i] = NULL; } first_bus->next = NULL; pci_state->num_buses = 1; pci_state->bus_list[0] = first_bus; for (i = 1; i < NUM_BUS; i++) { pci_state->bus_list[i] = NULL; } } //Lei static void init_pci_internal(struct pci_internal * pci_state) { pci_state->config_address = 0x00; //Not sure???? init_pci_bus(pci_state); } static int vpci_set_defaults(struct vm_device * dev) { PrintDebug("vpci: set defaults\n"); return 0; } static int vpci_reset_device(struct vm_device * dev) { PrintDebug("vpci: reset device\n"); vpci_set_defaults(dev); return 0; } static int vpci_start_device(struct vm_device * dev) { PrintDebug("vpci: start device\n"); return 0; } // TODO: This should be static int vpci_stop_device(struct vm_device * dev) { PrintDebug("vpci: stop device\n"); return 0; } // TODO: this should be static? int vpci_init_device(struct vm_device * dev) { struct pci_internal *pci_state = NULL; int i = 0; PrintDebug("vpci: init_device\n"); pci_state = (struct pci_internal *)dev->private_data; init_pci_internal(pci_state); init_fake_device(pci_state); //Chang for (i = 0; i < 4; i++){ v3_dev_hook_io(dev, PCI_CONFIG_ADDRESS + i, &vpci_addrport_read, &vpci_addrport_write); v3_dev_hook_io(dev, PCI_CONFIG_DATA + i, &vpci_dataport_read, &vpci_dataport_write); } return 0; } // TODO: This should be static int vpci_deinit_device(struct vm_device * dev) { int i = 0; for (i = 0; i < 4; i++){ v3_dev_unhook_io(dev, PCI_CONFIG_ADDRESS + i); v3_dev_unhook_io(dev, PCI_CONFIG_DATA + i); } vpci_reset_device(dev); return 0; } static struct vm_device_ops dev_ops = { .init = vpci_init_device, .deinit = vpci_deinit_device, .reset = vpci_reset_device, .start = vpci_start_device, .stop = vpci_stop_device, }; struct vm_device * v3_create_vpci() { struct pci_internal * pci_state = V3_Malloc(sizeof(struct pci_internal)); PrintDebug("PCI internal at %p\n",(void *)pci_state); struct vm_device * device = v3_create_device("PCI", &dev_ops, pci_state); return device; }