/* * Cisco router simulation platform. * Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr) * * PCI devices. * * Very interesting docs: * http://www.science.unitn.it/~fiorella/guidelinux/tlk/node72.html * http://www.science.unitn.it/~fiorella/guidelinux/tlk/node76.html */ #include #include #include #include #include #include "cpu.h" #include "vm.h" #include "dynamips.h" #include "memory.h" #include "device.h" #define DEBUG_PCI 1 #define GET_PCI_ADDR(offset,mask) ((pci_bus->pci_addr >> offset) & mask) /* Trigger a PCI device IRQ */ void pci_dev_trigger_irq(vm_instance_t *vm,struct pci_device *dev) { if (dev->irq != -1) vm_set_irq(vm,dev->irq); } /* Clear a PCI device IRQ */ void pci_dev_clear_irq(vm_instance_t *vm,struct pci_device *dev) { if (dev->irq != -1) vm_clear_irq(vm,dev->irq); } /* Swapping function */ static inline m_uint32_t pci_swap(m_uint32_t val,int swap) { return((swap) ? swap32(val) : val); } /* PCI bus lookup */ struct pci_bus *pci_bus_lookup(struct pci_bus *pci_bus_root,int bus) { struct pci_bus *next_bus,*cur_bus = pci_bus_root; struct pci_bridge *bridge; while(cur_bus != NULL) { if (cur_bus->bus == bus) return cur_bus; /* Try busses behind PCI bridges */ next_bus = NULL; for(bridge=cur_bus->bridge_list;bridge;bridge=bridge->next) { /* * Specific case: final bridge with no checking of secondary * bus number. Dynamically programming. */ if (bridge->skip_bus_check) { pci_bridge_set_bus_info(bridge,cur_bus->bus,bus,bus); bridge->skip_bus_check = FALSE; return bridge->pci_bus; } if ((bus >= bridge->sec_bus) && (bus <= bridge->sub_bus)) { next_bus = bridge->pci_bus; break; } } cur_bus = next_bus; } return NULL; } /* PCI device local lookup */ struct pci_device *pci_dev_lookup_local(struct pci_bus *pci_bus, int device,int function) { struct pci_device *dev; for(dev=pci_bus->dev_list;dev;dev=dev->next) if ((dev->device == device) && (dev->function == function)) return dev; return NULL; } /* PCI Device lookup */ struct pci_device *pci_dev_lookup(struct pci_bus *pci_bus_root, int bus,int device,int function) { struct pci_bus *req_bus; /* Find, try to find the request bus */ if (!(req_bus = pci_bus_lookup(pci_bus_root,bus))) return NULL; /* Walk through devices present on this bus */ return pci_dev_lookup_local(req_bus,device,function); } /* Handle the address register access */ void pci_dev_addr_handler(cpu_gen_t *cpu,struct pci_bus *pci_bus, u_int op_type,int swap,m_uint64_t *data) { if (op_type == MTS_WRITE) pci_bus->pci_addr = pci_swap(*data,swap); else *data = pci_swap(pci_bus->pci_addr,swap); } /* * Handle the data register access. * * The address of requested register is first written at address 0xcf8 * (with pci_dev_addr_handler). * * The data is read/written at address 0xcfc. */ void pci_dev_data_handler(cpu_gen_t *cpu,struct pci_bus *pci_bus, u_int op_type,int swap,m_uint64_t *data) { struct pci_device *dev; int bus,device,function,reg; if (op_type == MTS_READ) *data = 0x0; /* * http://www.mega-tokyo.com/osfaq2/index.php/PciSectionOfPentiumVme * * 31 : Enable Bit * 30 - 24 : Reserved * 23 - 16 : Bus Number * 15 - 11 : Device Number * 10 - 8 : Function Number * 7 - 2 : Register Number * 1 - 0 : always 00 */ bus = GET_PCI_ADDR(16,0xff); device = GET_PCI_ADDR(11,0x1f); function = GET_PCI_ADDR(8,0x7); reg = GET_PCI_ADDR(0,0xff); /* Find the corresponding PCI device */ dev = pci_dev_lookup(pci_bus,bus,device,function); #if DEBUG_PCI if (op_type == MTS_READ) { cpu_log(cpu,"PCI","read request at pc=0x%llx: " "bus=%d,device=%d,function=%d,reg=0x%2.2x\n", cpu_get_pc(cpu), bus, device, function, reg); } else { cpu_log(cpu,"PCI","write request (data=0x%8.8x) at pc=0x%llx: " "bus=%d,device=%d,function=%d,reg=0x%2.2x\n", pci_swap(*data,swap), cpu_get_pc(cpu), bus, device, function, reg); } #endif if (!dev) { if (op_type == MTS_READ) { cpu_log(cpu,"PCI","read request for unknown device at pc=0x%llx " "(bus=%d,device=%d,function=%d,reg=0x%2.2x).\n", cpu_get_pc(cpu), bus, device, function, reg); } else { cpu_log(cpu,"PCI","write request (data=0x%8.8x) for unknown device " "at pc=0x%llx (bus=%d,device=%d,function=%d,reg=0x%2.2x).\n", pci_swap(*data,swap), cpu_get_pc(cpu), bus, device, function, reg); } /* Returns an invalid device ID */ if ((op_type == MTS_READ) && (reg == PCI_REG_ID)) *data = 0xffffffff; } else { if (op_type == MTS_WRITE) { if (dev->write_register != NULL) dev->write_register(cpu,dev,reg,pci_swap(*data,swap)); } else { if (reg == PCI_REG_ID) *data = pci_swap((dev->product_id << 16) | dev->vendor_id,swap); else { if (dev->read_register != NULL) *data = pci_swap(dev->read_register(cpu,dev,reg),swap); } } } } /* Add a PCI bridge */ struct pci_bridge *pci_bridge_add(struct pci_bus *pci_bus) { struct pci_bridge *bridge; if (!pci_bus) return NULL; if (!(bridge = malloc(sizeof(*bridge)))) { fprintf(stderr,"pci_bridge_add: unable to create new PCI bridge.\n"); return NULL; } memset(bridge,0,sizeof(*bridge)); bridge->pri_bus = pci_bus->bus; bridge->sec_bus = -1; bridge->sub_bus = -1; bridge->pci_bus = NULL; /* Insert the bridge in the double-linked list */ bridge->next = pci_bus->bridge_list; bridge->pprev = &pci_bus->bridge_list; if (pci_bus->bridge_list != NULL) pci_bus->bridge_list->pprev = &bridge->next; pci_bus->bridge_list = bridge; return bridge; } /* Remove a PCI bridge from the double-linked list */ static inline void pci_bridge_remove_from_list(struct pci_bridge *bridge) { if (bridge->next) bridge->next->pprev = bridge->pprev; if (bridge->pprev) *(bridge->pprev) = bridge->next; } /* Remove a PCI bridge */ void pci_bridge_remove(struct pci_bridge *bridge) { if (bridge != NULL) { pci_bridge_remove_from_list(bridge); free(bridge); } } /* Map secondary bus to a PCI bridge */ void pci_bridge_map_bus(struct pci_bridge *bridge,struct pci_bus *pci_bus) { if (bridge != NULL) { bridge->pci_bus = pci_bus; if (bridge->pci_bus != NULL) bridge->pci_bus->bus = bridge->sec_bus; } } /* Set PCI bridge bus info */ void pci_bridge_set_bus_info(struct pci_bridge *bridge, int pri_bus,int sec_bus,int sub_bus) { if (bridge != NULL) { bridge->pri_bus = pri_bus; bridge->sec_bus = sec_bus; bridge->sub_bus = sub_bus; if (bridge->pci_bus != NULL) bridge->pci_bus->bus = bridge->sec_bus; } } /* Add a PCI device */ struct pci_device * pci_dev_add(struct pci_bus *pci_bus,char *name, u_int vendor_id,u_int product_id, int device,int function,int irq, void *priv_data,pci_init_t init, pci_reg_read_t read_register, pci_reg_write_t write_register) { struct pci_device *dev; if (!pci_bus) return NULL; if ((dev = pci_dev_lookup_local(pci_bus,device,function)) != NULL) { fprintf(stderr,"pci_dev_add: bus %s, device %d, function %d already " "registered (device '%s').\n", pci_bus->name,device,function,dev->name); return NULL; } /* we can create safely the new device */ if (!(dev = malloc(sizeof(*dev)))) { fprintf(stderr,"pci_dev_add: unable to create new PCI device.\n"); return NULL; } memset(dev,0,sizeof(*dev)); dev->name = name; dev->vendor_id = vendor_id; dev->product_id = product_id; dev->pci_bus = pci_bus; dev->device = device; dev->function = function; dev->irq = irq; dev->priv_data = priv_data; dev->init = init; dev->read_register = read_register; dev->write_register = write_register; /* Insert the device in the double-linked list */ dev->next = pci_bus->dev_list; dev->pprev = &pci_bus->dev_list; if (pci_bus->dev_list != NULL) pci_bus->dev_list->pprev = &dev->next; pci_bus->dev_list = dev; if (init) init(dev); return dev; } /* Add a basic PCI device that just returns a Vendor/Product ID */ struct pci_device * pci_dev_add_basic(struct pci_bus *pci_bus, char *name,u_int vendor_id,u_int product_id, int device,int function) { return(pci_dev_add(pci_bus,name,vendor_id,product_id, device,function,-1,NULL, NULL,NULL,NULL)); } /* Remove a device from the double-linked list */ static inline void pci_dev_remove_from_list(struct pci_device *dev) { if (dev->next) dev->next->pprev = dev->pprev; if (dev->pprev) *(dev->pprev) = dev->next; } /* Remove a PCI device */ void pci_dev_remove(struct pci_device *dev) { if (dev != NULL) { pci_dev_remove_from_list(dev); free(dev); } } /* Remove a PCI device given its ID (bus,device,function) */ int pci_dev_remove_by_id(struct pci_bus *pci_bus, int bus,int device,int function) { struct pci_device *dev; if (!(dev = pci_dev_lookup(pci_bus,bus,device,function))) return(-1); pci_dev_remove(dev); return(0); } /* Remove a PCI device given its name */ int pci_dev_remove_by_name(struct pci_bus *pci_bus,char *name) { struct pci_device *dev,*next; int count = 0; for(dev=pci_bus->dev_list;dev;dev=next) { next = dev->next; if (!strcmp(dev->name,name)) { pci_dev_remove(dev); count++; } } return(count); } /* Create a PCI bus */ struct pci_bus *pci_bus_create(char *name,int bus) { struct pci_bus *d; if (!(d = malloc(sizeof(*d)))) { fprintf(stderr,"pci_bus_create: unable to create PCI info.\n"); return NULL; } memset(d,0,sizeof(*d)); d->name = strdup(name); d->bus = bus; return d; } /* Delete a PCI bus */ void pci_bus_remove(struct pci_bus *pci_bus) { struct pci_device *dev,*next; struct pci_bridge *bridge,*next_bridge; if (pci_bus) { /* Remove all devices */ for(dev=pci_bus->dev_list;dev;dev=next) { next = dev->next; free(dev); } /* Remove all bridges */ for(bridge=pci_bus->bridge_list;bridge;bridge=next_bridge) { next_bridge = bridge->next; free(bridge); } /* Free the structure itself */ free(pci_bus->name); free(pci_bus); } } /* Read a configuration register of a PCI bridge */ static m_uint32_t pci_bridge_read_reg(cpu_gen_t *cpu,struct pci_device *dev, int reg) { struct pci_bridge *bridge = dev->priv_data; m_uint32_t val = 0; switch(reg) { case 0x18: return(bridge->cfg_reg_bus); default: if (bridge->fallback_read != NULL) val = bridge->fallback_read(cpu,dev,reg); /* Returns appropriate PCI bridge class code if nothing defined */ if ((reg == 0x08) && !val) val = 0x06040000; return(val); } } /* Write a configuration register of a PCI bridge */ static void pci_bridge_write_reg(cpu_gen_t *cpu,struct pci_device *dev, int reg,m_uint32_t value) { struct pci_bridge *bridge = dev->priv_data; u_int pri_bus,sec_bus,sub_bus; switch(reg) { case 0x18: bridge->cfg_reg_bus = value; sub_bus = (value >> 16) & 0xFF; sec_bus = (value >> 8) & 0xFF; pri_bus = value & 0xFF; /* Modify the PCI bridge settings */ vm_log(cpu->vm,"PCI", "PCI bridge %d,%d,%d -> pri: %2.2u, sec: %2.2u, sub: %2.2u\n", dev->pci_bus->bus,dev->device,dev->function, pri_bus,sec_bus,sub_bus); pci_bridge_set_bus_info(bridge,pri_bus,sec_bus,sub_bus); break; default: if (bridge->fallback_write != NULL) bridge->fallback_write(cpu,dev,reg,value); } } /* Create a PCI bridge device */ struct pci_device *pci_bridge_create_dev(struct pci_bus *pci_bus,char *name, u_int vendor_id,u_int product_id, int device,int function, struct pci_bus *sec_bus, pci_reg_read_t fallback_read, pci_reg_write_t fallback_write) { struct pci_bridge *bridge; struct pci_device *dev; /* Create the PCI bridge structure */ if (!(bridge = pci_bridge_add(pci_bus))) return NULL; /* Create the PCI device corresponding to the bridge */ dev = pci_dev_add(pci_bus,name,vendor_id,product_id,device,function,-1, bridge,NULL,pci_bridge_read_reg,pci_bridge_write_reg); if (!dev) goto err_pci_dev; /* Keep the associated PCI device for this bridge */ bridge->pci_dev = dev; /* Set the fallback functions */ bridge->fallback_read = fallback_read; bridge->fallback_write = fallback_write; /* Map the secondary bus (disabled at startup) */ pci_bridge_map_bus(bridge,sec_bus); return dev; err_pci_dev: pci_bridge_remove(bridge); return NULL; } /* Show PCI device list of the specified bus */ static void pci_bus_show_dev_list(struct pci_bus *pci_bus) { struct pci_device *dev; struct pci_bridge *bridge; char bus_id[32]; if (!pci_bus) return; if (pci_bus->bus != -1) { snprintf(bus_id,sizeof(bus_id),"%2d",pci_bus->bus); } else { strcpy(bus_id,"XX"); } for(dev=pci_bus->dev_list;dev;dev=dev->next) { printf(" %-18s: ID %4.4x:%4.4x, Bus %s, Dev. %2d, Func. %2d", dev->name,dev->vendor_id,dev->product_id, bus_id,dev->device,dev->function); if (dev->irq != -1) printf(", IRQ: %d\n",dev->irq); else printf("\n"); } for(bridge=pci_bus->bridge_list;bridge;bridge=bridge->next) pci_bus_show_dev_list(bridge->pci_bus); } /* Show PCI device list */ void pci_dev_show_list(struct pci_bus *pci_bus) { if (!pci_bus) return; printf("PCI Bus \"%s\" Device list:\n",pci_bus->name); pci_bus_show_dev_list(pci_bus); printf("\n"); }