//p40fpga``+ //ug`pcie40_driver.description` // ``pcie40.ko`` is the driver for the PCIe40 data acquisition card. //dg`wt.pcie40.fpga.init` When the kernel module is loaded, p40fpga`pcie40_init` is immediately called. Its dual at unload time is p40fpga`pcie40_exit` . The actual PCI device management happens in p40fpga`pcie40_probe` and p40fpga`pcie40_remove` . #define P40_FMT "P40:%s(): " #include "pcie40_driver_common.h" #include <linux/init.h> #include <linux/module.h> //ug`pcie40_driver.synopsis` // modprobe *pcie40* [ mainmibs=_M_ ] [ metamibs=_M_ ] static LIST_HEAD(pcie40_inst_list); static DEFINE_SPINLOCK(pcie40_inst_list_lock); static unsigned long pcie40_inst_list_lock_flags; //+`pcie40_ids` <? static const struct pci_device_id pcie40_ids[] = { { PCI_DEVICE(0x10DC, 0xCE40), }, { 0, } }; MODULE_DEVICE_TABLE(pci, pcie40_ids);//?> //ug`pcie40_driver.sysfs`link _ /sys/devices/.../pcie40_link:: // Link identifier for this interface within one PCIe40 board (0 for the primary and 1 for the secondary). static ssize_t attr_show_link(struct device *dev, struct device_attribute *attr, char *buf) { struct pci_dev *pci = container_of(dev, struct pci_dev, dev); struct pcie40_state *state = pci_get_drvdata(pci); return sprintf(buf, "%d", state ? state->link_id : -1); } static DEVICE_ATTR(pcie40_link, S_IRUGO, attr_show_link, NULL); //ug`pcie40_driver.sysfs`interface _ /sys/devices/.../pcie40_interface:: // Interface identifier allocated by the driver, this value uniquely identifies a PCIe40 interface within the machine. static ssize_t attr_show_interface(struct device *dev, struct device_attribute *attr, char *buf) { struct pci_dev *pci = container_of(dev, struct pci_dev, dev); struct pcie40_state *state = pci_get_drvdata(pci); return sprintf(buf, "%d", state ? state->dev_id : -1); } static DEVICE_ATTR(pcie40_interface, S_IRUGO, attr_show_interface, NULL); //ug`pcie40_driver.sysfs`loaded _ /sys/devices/.../pcie40_loaded:: // 1 if the FPGA BARs are readable, 0 if the FPGA has been reprogrammed and the driver must be reloaded. static ssize_t attr_show_loaded(struct device *dev, struct device_attribute *attr, char *buf) { struct pci_dev *pci = container_of(dev, struct pci_dev, dev); struct pcie40_state *state = pci_get_drvdata(pci); return sprintf(buf, "%d", pcie40_device_accessible(state) ? 1 : 0); } static DEVICE_ATTR(pcie40_loaded, S_IRUGO, attr_show_loaded, NULL); int pcie40_ecs_init(void); void pcie40_ecs_exit(void); int pcie40_ecs_probe(struct pci_dev *dev, const struct pci_device_id *id); void pcie40_ecs_remove(struct pci_dev *dev); int pcie40_daq_init(void); void pcie40_daq_exit(void); int pcie40_daq_probe(struct pci_dev *dev, const struct pci_device_id *id); void pcie40_daq_remove(struct pci_dev *dev); //+`pcie40_probe` Scan PCI bus to detect PCIe40 board. static int pcie40_probe(struct pci_dev *dev, const struct pci_device_id *id)//;?> { int rc = 0; struct pcie40_state *state = NULL, *li, *lp = NULL; int bar; printk(P40_DIAG "found PCI device, vendor: %08X device: %08X\n", P40_PARM, id->vendor, id->device); //? This function allocates a p40driver`pcie40_state` instance used to track the state of this device within the kernel. state = kzalloc(sizeof(struct pcie40_state), GFP_KERNEL); if (IS_ERR(state)) { printk(P40_ERR "kzalloc()\n", P40_PARM); rc = PTR_ERR(state); goto err_kzalloc; } printk(P40_DIAG "state = 0x%p\n", P40_PARM, state); INIT_LIST_HEAD(&state->list); rc = pci_enable_device(dev); if (rc) { printk(P40_DIAG "pci_enable_device() failed\n", P40_PARM); goto err_pci_enable_device; } pci_set_master(dev); // Without this, no interrupts will be received!!! //"The key difference that _exclusive makes it that userspace is explicitly not allowed to map the resource via /dev/mem or sysfs." rc = pci_request_selected_regions_exclusive(dev, P40_COMMON_BARS_MASK, P40_DRV_NAME); if (rc) { printk(P40_WARN "unable to reserve DAQ regions\n", P40_PARM); goto err_pci_request_regions_daq; } printk(P40_INFO "initializing BARs\n", P40_PARM); //? The state is initialized with the position and size of all PCI BARs. for (bar = 0; bar < P40_MAX_BAR; ++bar) { state->bar_start[bar] = pci_resource_start(dev, bar); state->bar_size[bar] = pci_resource_len(dev, bar); //TODO: print BAR information } if (!state->bar_size[1]) { printk(P40_ERR "no BAR1 detected!\n", P40_PARM); rc = -1; goto err_no_bar1; } //? BAR0, if present, is mapped inside the kernel to be accessible by the SCA interface (in addition, both BAR0 and BAR2 are accessible by userspace via memory mapped access). if (state->bar_start[0] && state->bar_size[0]) { printk(P40_INFO "pci_iomap() BAR0 (%lu bytes)\n", P40_PARM, state->bar_size[1]); state->bar0_regs = pci_iomap(dev, 0, state->bar_size[0]); if (state->bar0_regs == NULL) { rc = -1; goto err_bar0_iomap; } } //? BAR1 is always mapped inside the kernel as it's used directly by DAQ interface. printk(P40_INFO "pci_iomap() BAR1 (%lu bytes)\n", P40_PARM, state->bar_size[1]); state->bar1_regs = pci_iomap(dev, 1, state->bar_size[1]); if (state->bar1_regs == NULL) { rc = -1; goto err_bar1_iomap; } //? Using this mapping, the driver ensures that PCIe registers on the FPGA can be accessed. if (!pcie40_device_accessible(state)) { rc = -1; printk(P40_ERR "Device detected but unreadable, please re-enumerate bus to continue\n", P40_PARM); goto err_access; } spin_lock_irqsave(&pcie40_inst_list_lock, pcie40_inst_list_lock_flags); //? Then it reads the regmap`pcie.dma_ctrl.link_id` register to identify which PCIe link from the FPGA is being probed. state->link_id = pcie40_read32_ctrl(state, P40_DMA_CTRL_OFF_LINK_ID); //? Using this information, a unique interface identifier is allocated to the PCIe link. if (state->link_id == 0) { state->dev_id = 0; //? Interfaces with PCIe link 0 get an even interface id. } else { state->dev_id = 1; //? Interfaces on PCIe link 1 get an odd interface id. } //? The driver always allocates the lowest available interface id. list_for_each_entry(li, &pcie40_inst_list, list) { if ((state->dev_id & 1) == (li->dev_id & 1)) { if (state->dev_id == li->dev_id) { state->dev_id += 2; } } if (lp) { if (state->dev_id < lp->dev_id) { list_add_tail(&state->list, &lp->list); break; } } if (state->dev_id < li->dev_id) { list_add_tail(&state->list, &li->list); break; } lp = li; } if (list_empty(&state->list)) { list_add_tail(&state->list, &pcie40_inst_list); } spin_unlock_irqrestore(&pcie40_inst_list_lock, pcie40_inst_list_lock_flags); state->pci_dev = dev; pci_set_drvdata(dev, state); //? Finally it calls the probing logic of the subdrivers via p40fpga`pcie40_ecs_probe` and p40fpga`pcie40_daq_probe` . pcie40_ecs_probe(dev, id); pcie40_daq_probe(dev, id); device_create_file(&dev->dev, &dev_attr_pcie40_link); device_create_file(&dev->dev, &dev_attr_pcie40_interface); device_create_file(&dev->dev, &dev_attr_pcie40_loaded); //? After initializing the subdrivers, this function always returns success, this is to ensure that p40fpga`pcie40_remove` is always called also in case only some subdrivers are loaded. return 0; err_access: if (state->bar_size[1]) { iounmap(state->bar1_regs); state->bar1_regs = NULL; } err_bar1_iomap: if (state->bar_size[0]) { iounmap(state->bar0_regs); state->bar0_regs = NULL; } err_bar0_iomap: err_no_bar1: pci_release_selected_regions(dev, P40_COMMON_BARS_MASK); err_pci_request_regions_daq: pci_disable_device(dev); err_pci_enable_device: kfree(state); err_kzalloc: return rc; } //+`pcie40_remove` Remove PCIe40 board from kernel. static void pcie40_remove(struct pci_dev *dev)//;?> { struct pcie40_state *state = pci_get_drvdata(dev); spin_lock_irqsave(&pcie40_inst_list_lock, pcie40_inst_list_lock_flags); list_del(&state->list); spin_unlock_irqrestore(&pcie40_inst_list_lock, pcie40_inst_list_lock_flags); device_remove_file(&dev->dev, &dev_attr_pcie40_loaded); device_remove_file(&dev->dev, &dev_attr_pcie40_interface); device_remove_file(&dev->dev, &dev_attr_pcie40_link); //? First the submodules are uninitialized using p40fpga`pcie40_daq_remove` and p40fpga`pcie40_ecs_remove` . pcie40_daq_remove(dev); pcie40_ecs_remove(dev); //? BAR0 and BAR1 are unmapped using ``iounmap``. if (state->bar_size[0]) { iounmap(state->bar0_regs); state->bar0_regs = NULL; } if (state->bar_size[1]) { iounmap(state->bar1_regs); state->bar1_regs = NULL; } printk(P40_INFO "releasing PCI regions\n", P40_PARM); pci_release_selected_regions(dev, P40_COMMON_BARS_MASK); //? Finally the PCI device is disabled pci_disable_device(dev); //? and the p40driver`pcie40_state` memory is freed. kfree(state); } static struct pci_driver pcie40_pci_driver = { .name = P40_DRV_NAME, .id_table = pcie40_ids, .probe = pcie40_probe, .remove = pcie40_remove, }; //+`pcie40_init` Initialize subdrivers and register PCIe driver with kernel. static int __init pcie40_init(void)//;?> { int rc = 0; //? The first module to be initialized is the ECS, using p40driver`pcie40_ecs_init` . rc = pcie40_ecs_init(); if (rc < 0) return rc; //? Followed by the DAQ, using p40driver`pcie40_daq_init` . rc = pcie40_daq_init(); if (rc < 0) return rc; //? The driver is registered with the kernel using ``pci_register_driver``, its argument also contains the PCI device ids that correspond to the PCIe40 firmware (see p40fpga`pcie40_ids` ). rc = pci_register_driver(&pcie40_pci_driver); if (rc < 0) return rc; return rc; } //+`pcie40_exit` Unregister PCIe driver and uninitialize subdrivers. static void __exit pcie40_exit(void)//?> { pci_unregister_driver(&pcie40_pci_driver); pcie40_daq_exit(); pcie40_ecs_exit(); } //+`pcie40_init` module_init(pcie40_init); //+`pcie40_exit` module_exit(pcie40_exit); MODULE_VERSION(DAQ40_VER_REL); MODULE_LICENSE("GPL"); //TODO: MODULE_AUTHOR //TODO: MODULE_DESCRIPTION