cndm: Implement mmap and ioctl

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich
2026-01-01 15:07:50 -08:00
parent a0e6ac2c35
commit 3e421e3cdd
4 changed files with 182 additions and 16 deletions

View File

@@ -45,8 +45,9 @@ struct cndm_dev {
struct net_device *ndev[32]; struct net_device *ndev[32];
void __iomem *bar; resource_size_t hw_regs_size;
resource_size_t bar_len; phys_addr_t hw_regs_phys;
void __iomem *hw_addr;
u32 port_count; u32 port_count;
u32 port_offset; u32 port_offset;

View File

@@ -9,6 +9,7 @@ Authors:
*/ */
#include "cndm.h" #include "cndm.h"
#include "cndm_ioctl.h"
#include <linux/uaccess.h> #include <linux/uaccess.h>
@@ -28,8 +29,108 @@ static int cndm_release(struct inode *inode, struct file *file)
return 0; return 0;
} }
static int cndm_mmap(struct file *file, struct vm_area_struct *vma)
{
struct miscdevice *miscdev = file->private_data;
struct cndm_dev *cdev = container_of(miscdev, struct cndm_dev, misc_dev);
int index;
u64 pgoff, req_len, req_start;
index = vma->vm_pgoff >> (40 - PAGE_SHIFT);
req_len = vma->vm_end - vma->vm_start;
pgoff = vma->vm_pgoff & ((1U << (40 - PAGE_SHIFT)) - 1);
req_start = pgoff << PAGE_SHIFT;
if (vma->vm_end < vma->vm_start)
return -EINVAL;
if ((vma->vm_flags & VM_SHARED) == 0)
return -EINVAL;
switch (index) {
case 0:
if (req_start + req_len > cdev->hw_regs_size)
return -EINVAL;
return io_remap_pfn_range(vma, vma->vm_start,
(cdev->hw_regs_phys >> PAGE_SHIFT) + pgoff,
req_len, pgprot_noncached(vma->vm_page_prot));
default:
return -EINVAL;
}
return -EINVAL;
}
static long cndm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct miscdevice *miscdev = file->private_data;
struct cndm_dev *cdev = container_of(miscdev, struct cndm_dev, misc_dev);
size_t minsz;
if (cmd == CNDM_IOCTL_GET_API_VERSION) {
// Get API version
return CNDM_IOCTL_API_VERSION;
} else if (cmd == CNDM_IOCTL_GET_DEVICE_INFO) {
// Get device information
struct cndm_ioctl_device_info info;
minsz = offsetofend(struct cndm_ioctl_device_info, num_irqs);
if (copy_from_user(&info, (void __user *)arg, minsz))
return -EFAULT;
if (info.argsz < minsz)
return -EINVAL;
info.flags = 0;
info.num_regions = 1;
info.num_irqs = 0;
return copy_to_user((void __user *)arg, &info, minsz) ? -EFAULT : 0;
} else if (cmd == CNDM_IOCTL_GET_REGION_INFO) {
// Get region information
struct cndm_ioctl_region_info info;
minsz = offsetofend(struct cndm_ioctl_region_info, name);
if (copy_from_user(&info, (void __user *)arg, minsz))
return -EFAULT;
if (info.argsz < minsz)
return -EINVAL;
info.flags = 0;
info.type = CNDM_REGION_TYPE_UNIMPLEMENTED;
info.next = 0;
info.child = 0;
info.size = 0;
info.offset = ((u64)info.index) << 40;
info.name[0] = 0;
switch (info.index) {
case 0:
info.type = CNDM_REGION_TYPE_NIC_CTRL;
info.next = 0;
info.child = 0;
info.size = cdev->hw_regs_size;
info.offset = ((u64)info.index) << 40;
strscpy(info.name, "ctrl", sizeof(info.name));
break;
default:
return -EINVAL;
}
return copy_to_user((void __user *)arg, &info, minsz) ? -EFAULT : 0;
}
return -EINVAL;
}
const struct file_operations cndm_fops = { const struct file_operations cndm_fops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.open = cndm_open, .open = cndm_open,
.release = cndm_release, .release = cndm_release,
.mmap = cndm_mmap,
.unlocked_ioctl = cndm_ioctl,
}; };

View File

@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL */
/*
Copyright (c) 2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
#ifndef CNDM_IOCTL_H
#define CNDM_IOCTL_H
#include <linux/types.h>
#define CNDM_IOCTL_API_VERSION 0
#define CNDM_IOCTL_TYPE 0x63
#define CNDM_IOCTL_BASE 0xC0
enum {
CNDM_REGION_TYPE_UNIMPLEMENTED = 0x00000000,
CNDM_REGION_TYPE_CTRL = 0x00001000,
CNDM_REGION_TYPE_NIC_CTRL = 0x00001001,
CNDM_REGION_TYPE_APP_CTRL = 0x00001002,
};
// get API version
#define CNDM_IOCTL_GET_API_VERSION _IO(CNDM_IOCTL_TYPE, CNDM_IOCTL_BASE + 0)
// get device information
struct cndm_ioctl_device_info {
__u32 argsz;
__u32 flags;
__u32 fw_id;
__u32 fw_ver;
__u32 board_id;
__u32 board_ver;
__u32 build_date;
__u32 git_hash;
__u32 rel_info;
__u32 num_regions;
__u32 num_irqs;
};
#define CNDM_IOCTL_GET_DEVICE_INFO _IO(CNDM_IOCTL_TYPE, CNDM_IOCTL_BASE + 1)
// get region information
struct cndm_ioctl_region_info {
__u32 argsz;
__u32 flags;
__u32 index;
__u32 type;
__u32 next;
__u32 child;
__u32 size;
__u64 offset;
__u8 name[32];
};
#define CNDM_IOCTL_GET_REGION_INFO _IO(CNDM_IOCTL_TYPE, CNDM_IOCTL_BASE + 2)
#endif

View File

@@ -52,9 +52,9 @@ static int cndm_common_probe(struct cndm_dev *cdev)
devlink_register(devlink, dev); devlink_register(devlink, dev);
#endif #endif
cdev->port_count = ioread32(cdev->bar + 0x0100); cdev->port_count = ioread32(cdev->hw_addr + 0x0100);
cdev->port_offset = ioread32(cdev->bar + 0x0104); cdev->port_offset = ioread32(cdev->hw_addr + 0x0104);
cdev->port_stride = ioread32(cdev->bar + 0x0108); cdev->port_stride = ioread32(cdev->hw_addr + 0x0108);
dev_info(dev, "Port count: %d", cdev->port_count); dev_info(dev, "Port count: %d", cdev->port_count);
dev_info(dev, "Port offset: 0x%x", cdev->port_offset); dev_info(dev, "Port offset: 0x%x", cdev->port_offset);
@@ -63,7 +63,7 @@ static int cndm_common_probe(struct cndm_dev *cdev)
for (k = 0; k < cdev->port_count; k++) { for (k = 0; k < cdev->port_count; k++) {
struct net_device *ndev; struct net_device *ndev;
ndev = cndm_create_netdev(cdev, k, cdev->bar + cdev->port_offset + (cdev->port_stride*k)); ndev = cndm_create_netdev(cdev, k, cdev->hw_addr + cdev->port_offset + (cdev->port_stride*k));
if (IS_ERR_OR_NULL(ndev)) { if (IS_ERR_OR_NULL(ndev)) {
ret = PTR_ERR(ndev); ret = PTR_ERR(ndev);
goto fail_netdev; goto fail_netdev;
@@ -155,17 +155,18 @@ static int cndm_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto fail_regions; goto fail_regions;
} }
cdev->bar_len = pci_resource_len(pdev, 0); cdev->hw_regs_size = pci_resource_len(pdev, 0);
cdev->hw_regs_phys = pci_resource_start(pdev, 0);
dev_info(dev, "BAR size: %llu", cdev->bar_len); dev_info(dev, "Control BAR size: %llu", cdev->hw_regs_size);
cdev->bar = pci_ioremap_bar(pdev, 0); cdev->hw_addr = pci_ioremap_bar(pdev, 0);
if (!cdev->bar) { if (!cdev->hw_addr) {
ret = -ENOMEM; ret = -ENOMEM;
dev_err(dev, "Failed to map BAR 0"); dev_err(dev, "Failed to map control BAR");
goto fail_map_bars; goto fail_map_bars;
} }
if (ioread32(cdev->bar + 0x0000) == 0xffffffff) { if (ioread32(cdev->hw_addr + 0x0000) == 0xffffffff) {
ret = -EIO; ret = -EIO;
dev_err(dev, "Device needs to be reset"); dev_err(dev, "Device needs to be reset");
goto fail_map_bars; goto fail_map_bars;
@@ -187,8 +188,8 @@ fail_common:
cndm_irq_deinit_pcie(cdev); cndm_irq_deinit_pcie(cdev);
fail_init_irq: fail_init_irq:
fail_map_bars: fail_map_bars:
if (cdev->bar) if (cdev->hw_addr)
pci_iounmap(pdev, cdev->bar); pci_iounmap(pdev, cdev->hw_addr);
pci_release_regions(pdev); pci_release_regions(pdev);
fail_regions: fail_regions:
pci_clear_master(pdev); pci_clear_master(pdev);
@@ -211,8 +212,8 @@ static void cndm_pci_remove(struct pci_dev *pdev)
cndm_common_remove(cdev); cndm_common_remove(cdev);
cndm_irq_deinit_pcie(cdev); cndm_irq_deinit_pcie(cdev);
if (cdev->bar) if (cdev->hw_addr)
pci_iounmap(pdev, cdev->bar); pci_iounmap(pdev, cdev->hw_addr);
pci_release_regions(pdev); pci_release_regions(pdev);
pci_clear_master(pdev); pci_clear_master(pdev);
pci_disable_device(pdev); pci_disable_device(pdev);