-
Patrick Robbe authoredPatrick Robbe authored
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