Skip to content
Snippets Groups Projects
main.c 10.00 KiB
//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