Skip to content
Snippets Groups Projects
flxnet_pcie.c 7.23 KiB
Newer Older
Elena Zhivun's avatar
Elena Zhivun committed
/*

	This is the network driver for host side
	to create a network interface over PCIe
Elena Zhivun's avatar
Elena Zhivun committed

	When module is loaded:
	1. Finds a FLX-182 PCIe card (vendor=0x10dc, device=0x0427)
	2. Enables the PCIe device, reserve and map the BAR if BAR #3 is available
	3. Register the peer with flxnet network device
	4. Repeats untill all the PCIe devices available are configured with a network interface
	5. If BAR #3 is not available, does nothing
Elena Zhivun's avatar
Elena Zhivun committed

*/

#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
Elena Zhivun's avatar
Elena Zhivun committed

#include "flxnet/flxnet.h"

Elena Zhivun's avatar
Elena Zhivun committed
extern struct flxnet_peer * flxnet_add_peer(void* __iomem base_address,
	int index, struct module *owner);
Elena Zhivun's avatar
Elena Zhivun committed

#define DRV_MODULE_NAME	"flxnet_pcie"

#define FLX_PCIE_MAGIC	0xDED133D0UL
Laura Rientsma's avatar
Laura Rientsma committed

Elena Zhivun's avatar
Elena Zhivun committed
struct pcie_flx_serial_device {
	unsigned long magic;		// structure ID for sanity checks 
Elena Zhivun's avatar
Elena Zhivun committed
	struct list_head list_head;	
	struct pci_dev *pdev;		// pci device struct from probe()
	void __iomem *bar;			// addresses for the relevant BAR
Elena Zhivun's avatar
Elena Zhivun committed
	int bar_idx;
	spinlock_t lock;			// protects concurrent access
Elena Zhivun's avatar
Elena Zhivun committed
	unsigned int flags;

	struct flxnet_peer * peer;
};

Laura Rientsma's avatar
Laura Rientsma committed
// which bar number the AXI serial device is mapped to
Elena Zhivun's avatar
Elena Zhivun committed

Laura Rientsma's avatar
Laura Rientsma committed
// which offset the AXI serial device is found at
Elena Zhivun's avatar
Elena Zhivun committed
static uint dev_base_addr = 0x0;

Laura Rientsma's avatar
Laura Rientsma committed
// list of FELIX devices
Elena Zhivun's avatar
Elena Zhivun committed
static LIST_HEAD(flx_devices);

static int configure_pcie_device(struct pci_dev *pdev, int index);
Elena Zhivun's avatar
Elena Zhivun committed
static void deconfigure_device(struct pcie_flx_serial_device *fdev);
static int map_single_bar(struct pcie_flx_serial_device *fdev, int idx);

Laura Rientsma's avatar
Laura Rientsma committed
static int flxnet_pcie_init(void) {
	int index = 0;
	int rv = -ENODEV;
	struct pci_dev *pdev; // previous device
	pdev = NULL; 		  // a new search for PCI devices is initiated by passing NULL as the pdev argument
	pr_info("flxnet_pcie: loading %s module\n", DRV_MODULE_NAME);	
	 
	// if a PCI device is found, a pointer to its device structure is returned
	pdev = pci_get_device(0x10dc, 0x0427, pdev);

	// pdev returns NULL when all the PCI devices are found and stops searching
	while (pdev != NULL) {
		pr_info("flxnet_pcie: found %s with index %d\n", pci_name(pdev), index);
		rv = configure_pcie_device(pdev, index);
		pci_dev_put(pdev);
			pr_warn("flxnet_pcie: failed to configure %s\n", pci_name(pdev));
		}

		pdev = pci_get_device(0x10dc, 0x0427, pdev);
		index++;

		if (pdev == NULL) {
			pr_info("flxnet_pcie: no more pcie devices found\n");
Elena Zhivun's avatar
Elena Zhivun committed
	return rv;	
}

// deconfigure pcie devices if necessary, remove module
Laura Rientsma's avatar
Laura Rientsma committed
static void flxnet_pcie_exit(void) {

Elena Zhivun's avatar
Elena Zhivun committed
	struct pcie_flx_serial_device *fdev, *tmp;
Elena Zhivun's avatar
Elena Zhivun committed

	pr_info("flxnet_pcie: removing %s module\n", DRV_MODULE_NAME);
Elena Zhivun's avatar
Elena Zhivun committed

	if (!list_empty(&flx_devices)) {
		pr_info("flxnet_pcie: list not empty - deconfiguring PCIe devices\n");
Elena Zhivun's avatar
Elena Zhivun committed
		list_for_each_entry_safe(fdev, tmp, &flx_devices, list_head) {
Elena Zhivun's avatar
Elena Zhivun committed
			deconfigure_device(fdev);
		}	
	}
	else {
		pr_info("flxnet_pcie: list is empty - nothing to deconfigure\n");
	}
Laura Rientsma's avatar
Laura Rientsma committed
static int configure_pcie_device(struct pci_dev *pdev, int index) {

Elena Zhivun's avatar
Elena Zhivun committed
	int rv;
	struct pcie_flx_serial_device *fdev;

	if (!pdev) {
		pr_err("flxnet_pcie: invalid PCIe device structure\n");
Elena Zhivun's avatar
Elena Zhivun committed
		return -EFAULT;
	}

	pr_info("flxnet_pcie: initializing the PCIe device\n");
Elena Zhivun's avatar
Elena Zhivun committed

	fdev = kzalloc(sizeof(struct pcie_flx_serial_device), GFP_KERNEL);
	if (!fdev)
		return -ENOMEM;

	spin_lock_init(&fdev->lock);
	fdev->magic = FLX_PCIE_MAGIC;
	fdev->pdev = pdev;
	fdev->bar_idx = bar_idx;
Elena Zhivun's avatar
Elena Zhivun committed
	INIT_LIST_HEAD(&fdev->list_head);
Elena Zhivun's avatar
Elena Zhivun committed

	pr_info("flxnet_pcie: enabling PCIe device\n");
Elena Zhivun's avatar
Elena Zhivun committed
	rv = pci_enable_device(pdev);
	if (rv) {
		pr_err("flxnet_pcie: pci_enable_device() failed, %d\n", rv);
Elena Zhivun's avatar
Elena Zhivun committed
		goto err_enable;
	}

	// reserve the selected bar for this driver
	pr_info("flxnet_pcie: reserving BAR memory\n");
Elena Zhivun's avatar
Elena Zhivun committed
	rv = pci_request_region(pdev, bar_idx, DRV_MODULE_NAME);
	if (rv) {
		pr_err("flxnet_pcie: pci_request_region() failed (%d)\n", rv);
Elena Zhivun's avatar
Elena Zhivun committed
		goto err_region;
	}		


	pr_info("flxnet_pcie: mapping BAR memory\n");
Elena Zhivun's avatar
Elena Zhivun committed
	rv = map_single_bar(fdev, bar_idx);
	if (rv < 0) {
Elena Zhivun's avatar
Elena Zhivun committed
	} else if (rv < MIN_MMAP_SIZE) {
		pr_err("flxnet_pcie: mapped region is too small (%d bytes < %d)\n",
Elena Zhivun's avatar
Elena Zhivun committed
			rv, MIN_MMAP_SIZE);
		goto err_peer;
	}

	// add this device to the network
	pr_info("flxnet_pcie: inserting peer into flxnet\n");
Elena Zhivun's avatar
Elena Zhivun committed
	fdev->peer = flxnet_add_peer(fdev->bar + dev_base_addr,
		index, THIS_MODULE);
Elena Zhivun's avatar
Elena Zhivun committed

	if (IS_ERR(fdev->peer)) {
		pr_err("flxnet_pcie: error inserting peer into flxnet\n");
Elena Zhivun's avatar
Elena Zhivun committed
		goto err_peer;
	}

	// device structure
	list_add(&fdev->list_head, &flx_devices);
	pr_info("flxnet_pcie: added peer to list flx_devices\n");
Elena Zhivun's avatar
Elena Zhivun committed
	return 0;

warn_nobar:
	pr_warn("flxnet_pcie: no network registered, disabling PCI device\n");
	pci_disable_device(pdev);
	pci_release_region(pdev, bar_idx);
	return 0;
Elena Zhivun's avatar
Elena Zhivun committed

err_peer:
	pr_info("flxnet_pcie: unmapping BAR memory\n");
Elena Zhivun's avatar
Elena Zhivun committed
	pci_iounmap(pdev, fdev->bar);

err_enable:
	pr_err("flxnet_pcie: pdev 0x%p, err %d\n", pdev, rv);
Elena Zhivun's avatar
Elena Zhivun committed
	kfree(fdev);
	return rv;

err_region:
	pci_disable_device(pdev);
	goto err_enable;
}


// unregister and deconfigure a felix serial device
Elena Zhivun's avatar
Elena Zhivun committed
static void deconfigure_device(struct pcie_flx_serial_device *fdev) {
	struct pci_dev * pdev;

	pr_info("flxnet_pcie: deconfiguring device %p\n", fdev);
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
	if (!fdev) {
		pr_err("flxnet_pcie: felix_flx_serial_device ptr is NULL\n");
Elena Zhivun's avatar
Elena Zhivun committed
		return;
	}

	if (fdev->magic != FLX_PCIE_MAGIC) {
		pr_err("flxnet_pcie: incorrect MAGIC value in deconfigure_device()\n");
Elena Zhivun's avatar
Elena Zhivun committed
		return;
	}

	pdev = fdev->pdev;

	if (!pdev) {
		pr_err("flxnet_pcie: pci_dev ptr is NULL\n");
Elena Zhivun's avatar
Elena Zhivun committed
		goto err_exit;
	}

	if (fdev->bar) {
		pci_iounmap(pdev, fdev->bar);		
	} else {
		pr_err("flxnet_pcie: BAR memory is not mapped\n");
Elena Zhivun's avatar
Elena Zhivun committed
	}

	pci_disable_device(pdev);
	pci_release_region(pdev, bar_idx);
	
err_exit:
	list_del(&fdev->list_head);
Elena Zhivun's avatar
Elena Zhivun committed
	kfree(fdev);
Laura Rientsma's avatar
Laura Rientsma committed
// map BAR containing the serial communication registers
static int map_single_bar(struct pcie_flx_serial_device *fdev, int idx) {

Elena Zhivun's avatar
Elena Zhivun committed
	struct pci_dev *pdev = fdev->pdev;
	resource_size_t bar_start = pci_resource_start(pdev, idx);
	resource_size_t bar_len = pci_resource_len(pdev, idx);
	resource_size_t map_len = bar_len;

	fdev->bar = NULL;

	// do not map BARs with length 0. Note that start MAY be 0!
Elena Zhivun's avatar
Elena Zhivun committed
	if (!bar_len) {
		pr_info("flxnet_pcie: BAR #%d is not present\n", idx);
Elena Zhivun's avatar
Elena Zhivun committed
		return -1;
	}

	// BAR size exceeds maximum desired mapping?
Elena Zhivun's avatar
Elena Zhivun committed
	if (bar_len > INT_MAX) {
		pr_info("flxnet_pcie: limit BAR %d mapping from %llu to %d bytes\n", idx,
Elena Zhivun's avatar
Elena Zhivun committed
			(u64)bar_len, INT_MAX);
		map_len = (resource_size_t)INT_MAX;
	}

	// map the full device memory or IO region into kernel virtual address space
Elena Zhivun's avatar
Elena Zhivun committed
	fdev->bar = pci_iomap(pdev, idx, map_len);

	if (!fdev->bar) {
		pr_info("flxnet_pcie: could not map BAR %d\n", idx);
Elena Zhivun's avatar
Elena Zhivun committed
		return -1;
	}

	pr_info("flxnet_pcie: BAR%d at 0x%llx mapped at 0x%p, length=%llu(/%llu)\n", idx,
Elena Zhivun's avatar
Elena Zhivun committed
		(u64)bar_start, fdev->bar, (u64)map_len, (u64)bar_len);

	return (int)map_len;
}


module_init(flxnet_pcie_init);
module_exit(flxnet_pcie_exit);
Elena Zhivun's avatar
Elena Zhivun committed
module_param(bar_idx, int, S_IRUSR);
MODULE_PARM_DESC(bar_idx, "BAR of the FIFO AXI device");
module_param(dev_base_addr, uint, S_IRUSR);
MODULE_PARM_DESC(dev_base_addr, "Base offset of the FIFO AXI device");

MODULE_AUTHOR("Elena Zhivun <elena.zhivun@cern.ch>");
MODULE_DESCRIPTION("Driver on host PC for establishing virtual network over PCIe, registers with flxnet_dev");
MODULE_LICENSE("GPL");