-
Patrick Robbe authoredPatrick Robbe authored
ecs.c 6.31 KiB
//p40fpga``+
#define P40_FMT "P40ECS:%s(): "
#define PCIE40_ECS_CLASS "lhcb_pcie40_ecs"
#include "ecs.h"
#include "pcie40_ioctl.h"
//dg`wt.pcie40.fpga.ecs` The ECS submodule implements the system calls behind the BAR0 and BAR2 device files. The submodule is initialized and uninitialized at the same time as the main module, through the p40driver`pcie40_ecs_init` and p40driver`pcie40_ecs_exit` functions. Likewise the ECS-specific probing logic is encapsulated in p40fpga`pcie40_ecs_probe` and p40fpga`pcie40_ecs_remove` .
static void pcie40_ecs_set_drvdata(struct pci_dev *pdev, struct pcie40_ecs_state *state)
{
struct pcie40_state *common = pci_get_drvdata(pdev);
common->ecs_state = state;
common->ecs_state->common = common;
}
static struct pcie40_ecs_state *pcie40_ecs_get_drvdata(struct pci_dev *pdev)
{
struct pcie40_state *common = pci_get_drvdata(pdev);
return common->ecs_state;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
static int ecs_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
#else
static long ecs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
#endif//+`ecs_ioctl`
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35))
struct inode *inode = filp->f_inode;
#endif
struct pcie40_ecs_state *state = filp->private_data;
int bar;
switch (iminor(inode)) {
case BAR0_CDEV_MINOR:
bar = 0;
break;
case BAR2_CDEV_MINOR:
bar = 2;
break;
default:
return -EINVAL;
}
//ioctl.pcie`P40_ECS_GET_BAR_SIZE`
if (cmd != P40_ECS_GET_BAR_SIZE) {
printk(P40_DIAG "invalid ioctl command\n", P40_PARM);
return -EINVAL;
}
printk(P40_INFO "ECS BAR size is %lu\n", P40_PARM, state->common->bar_size[bar]);
return state->common->bar_size[bar];
}
//+`ecs_mmap`
static int ecs_mmap(struct file* filp, struct vm_area_struct* vma)//;?>
{
int rc = 0;
struct pcie40_ecs_state *state = filp->private_data;
int bar;
switch (iminor(filp->f_path.dentry->d_inode)) {
case BAR0_CDEV_MINOR:
bar = 0;
break;
case BAR2_CDEV_MINOR:
bar = 2;
break;
default:
return -EINVAL;
}
//vma->vm_flags |= VM_IO | VM_RESERVED; //VM_DONTEXPAND | VM_DONTDUMP; for 3.11
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
printk(P40_INFO "VIRT=%lx PHYS=%lx SIZE=%lu\n", P40_PARM,
vma->vm_start, state->common->bar_start[bar], vma->vm_end - vma->vm_start);
//this needs a more recent kernel than what we have now
//rc = vm_iomap_memory(vma, vma->vm_start, vma->vm_end - vma->vm_start);
rc = io_remap_pfn_range(vma, vma->vm_start,
state->common->bar_start[bar] >> PAGE_SHIFT, vma->vm_end - vma->vm_start,
vma->vm_page_prot);
if (rc) {
printk(P40_DIAG "io_remap_pfn_range()\n", P40_PARM);
return rc;
}
//vma->vm_ops = &bar_vm_ops;
return 0;
}
static struct file_operations ecs_file_ops = {
.owner = THIS_MODULE,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
.ioctl = ecs_ioctl,
#else
.unlocked_ioctl = ecs_ioctl,
#endif
.mmap = ecs_mmap,
.open = ecs_open,
.release = ecs_release,
};
//+`pcie40_ecs_probe` Initialize ECS BARs and create device files.
int pcie40_ecs_probe(struct pci_dev *dev, const struct pci_device_id *id)//;?>
{
int rc = 0;
struct pcie40_state *common;
struct pcie40_ecs_state *state = NULL;
common = pci_get_drvdata(dev);
//? This function allocates a p40driver`pcie40_ecs_state` instance to keep ECS-specific state.
state = kzalloc(sizeof(struct pcie40_ecs_state), GFP_KERNEL);
if (IS_ERR(state)) {
printk(P40_ERR "kzalloc()\n", P40_PARM);
rc = PTR_ERR(state);
goto err_kzalloc;
}
state->common = common;
printk(P40_DIAG "state = 0x%p\n", P40_PARM, state);
//? It then requests exclusive access to the ECS BARs
rc = pci_request_selected_regions_exclusive(dev, P40_ECS_BARS_MASK, P40_DRV_NAME);
if (rc) {
printk(P40_WARN "unable to reserve ECS regions\n", P40_PARM);
goto err_pci_request_regions_ecs;
}
//? and allocates a range of minor numbers for its character devices.
rc = alloc_chrdev_region(&(state->dev_num), P40_ECS_CDEV_BASEMINOR, P40_ECS_CDEV_COUNT, P40_DRV_NAME);
if (rc < 0) {
printk(P40_ERR "alloc_chrdev_region()\n", P40_PARM);
goto err_alloc_chrdev_region;
}
//? One such device is created for BAR0
if (state->common->bar_size[0]) {
rc = pcie40_setup_cdev(pcie40_ecs_class, &(state->bar0_cdev), state->dev_num, BAR0_CDEV_MINOR, 0, BAR0_CDEV_NAME, state->common->dev_id, &ecs_file_ops);
if (rc < 0) {
goto err_bar0_dev;
}
}
//? and a second one for BAR2.
if (state->common->bar_size[2]) {
rc = pcie40_setup_cdev(pcie40_ecs_class, &(state->bar2_cdev), state->dev_num, BAR2_CDEV_MINOR, 2, BAR2_CDEV_NAME, state->common->dev_id, &ecs_file_ops);
if (rc < 0) {
goto err_bar2_dev;
}
}
pcie40_ecs_set_drvdata(dev, state);
return rc;
err_bar2_dev:
if (state->common->bar_size[0]) {
printk(P40_INFO "remove /dev/pcie40_%d_%s\n", P40_PARM, state->common->dev_id, BAR0_CDEV_NAME);
device_destroy(pcie40_ecs_class, MKDEV(MAJOR(state->dev_num), MINOR(state->dev_num)+BAR0_CDEV_MINOR));
}
err_bar0_dev:
unregister_chrdev_region(state->dev_num, P40_ECS_CDEV_COUNT);
err_alloc_chrdev_region:
pci_release_selected_regions(dev, P40_ECS_BARS_MASK);
err_pci_request_regions_ecs:
kfree(state);
err_kzalloc:
return rc;
}
//+`pcie40_ecs_remove` Destroy ECS device files.
void pcie40_ecs_remove(struct pci_dev *dev)//;?>
{
struct pcie40_ecs_state *state;
printk(P40_DIAG "pci_dev = 0x%p\n", P40_PARM, dev);
state = pcie40_ecs_get_drvdata(dev);
if (!dev || !state) {
printk(P40_DIAG "remove(dev = 0x%p) dev->driver_data = 0x%p\n", P40_PARM, dev, state);
return;
}
if (state->common->bar_size[2]) {
printk(P40_INFO "remove /dev/pcie40_%d_%s\n", P40_PARM, state->common->dev_id, BAR2_CDEV_NAME);
device_destroy(pcie40_ecs_class, MKDEV(MAJOR(state->dev_num), MINOR(state->dev_num)+BAR2_CDEV_MINOR));
}
if (state->common->bar_size[0]) {
printk(P40_INFO "remove /dev/pcie40_%d_%s\n", P40_PARM, state->common->dev_id, BAR0_CDEV_NAME);
device_destroy(pcie40_ecs_class, MKDEV(MAJOR(state->dev_num), MINOR(state->dev_num)+BAR0_CDEV_MINOR));
}
unregister_chrdev_region(state->dev_num, P40_ECS_CDEV_COUNT);
printk(P40_INFO "releasing PCI regions\n", P40_PARM);
pci_release_selected_regions(dev, P40_ECS_BARS_MASK);
kfree(state);
}