Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:a_faerber:machinelearning
degirum
lincdadrv_whitespace.diff
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File lincdadrv_whitespace.diff of Package degirum
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a9f06af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto + +*.c text +*.h text +Makefile text +*.sh text diff --git a/src/cdadrv.c b/src/cdadrv.c index 7e31633..e4c7b58 100644 --- a/src/cdadrv.c +++ b/src/cdadrv.c @@ -1,405 +1,410 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright(c) 2020 DeGirum Corp., Egor Pomozov. -// -// CDA linux driver mem blocks/mem maps and interrupt request handler -// -// This program is free software; you can redistribute it and/or modify it -// under the terms and conditions of the GNU General Public License, -// version 2, as published by the Free Software Foundation. -// -#include <linux/module.h> -#include <linux/errno.h> -#include <linux/init.h> -#include <linux/fs.h> - -#include "cdadrv.h" -#include "cdaioctl.h" - -MODULE_AUTHOR("DeGirum Corp., Egor Pomozov"); -MODULE_DESCRIPTION("CDA linux driver to access pci devices"); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.5.0.3"); -// The version has to be in the format n.n.n.n, where each n is a single digit - -#if LINUX_VERSION_CODE < KERNEL_VERSION(4,9,0) -#error Too old kernel -#endif - -static dev_t cdadev_first; -static const char cda_name[] = "cda"; -static int req_pci_did = 0; -static int req_pci_vid = 0; -static int test_probe = 0; - -#define CDA_DEV_MINOR_MAX 32 -static DEFINE_SPINLOCK(cdadevlist_sl); -static DEFINE_IDA(cdaminor_ida); -static LIST_HEAD(cdadevs); - -// Module parameters -module_param_named(did, req_pci_did, int, 0644); -MODULE_PARM_DESC(did, "Set required PCI device ID"); -module_param_named(vid, req_pci_vid, int, 0644); -MODULE_PARM_DESC(vid, "Set required PCI vendor ID"); -module_param_named(test_probe, test_probe, int, 0644); -MODULE_PARM_DESC(test_probe, "Check permissions to load driver"); - -static void cdadev_release(struct device *kdev); -static void cda_pci_remove(struct pci_dev *pcidev); -static int cda_pci_probe(struct pci_dev *pcidev, - const struct pci_device_id *id); - -static int cda_cdev_open(struct inode *ino, struct file *file); -static int cda_cdev_release(struct inode *ino, struct file *file); -static long cda_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg); - -static struct pci_device_id cda_pci_ids[] = { - { PCI_DEVICE(0x1f0d, 0x0100) }, - { PCI_DEVICE(0x1f0d, 0x8101) }, - { PCI_DEVICE(0x1f0d, 0x0101) }, - { PCI_DEVICE(0x10ee, 0x8011) }, - { PCI_DEVICE(0, 0) }, - { PCI_DEVICE(0, 0) }, -}; - -static struct pci_driver cda_pci = { - .name = cda_name, - .probe = cda_pci_probe, - .remove = cda_pci_remove, - .id_table = cda_pci_ids, -}; - -static struct file_operations cda_fileops = { - .owner = THIS_MODULE, - .open = cda_cdev_open, - .release = cda_cdev_release, - .unlocked_ioctl = cda_cdev_ioctl, - -}; - -static struct class cda_class = { - .name = cda_name, - .dev_release = cdadev_release, -}; -/* -static inline bool cda_kernel_is_locked_down(void) -{ -#ifdef CONFIG_LOCK_DOWN_KERNEL -#ifdef CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT / * fedora * / - return kernel_is_locked_down(NULL); -#elif CONFIG_EFI_SECURE_BOOT_LOCK_DOWN / * ubuntu * / - return kernel_is_locked_down(); -#else - return false; -#endif -#else - return false; -#endif -} -*/ -static void cdadev_free(struct cda_dev *cdadev) -{ - ida_simple_remove(&cdaminor_ida, cdadev->minor); - device_del(&cdadev->dev); - put_device(&cdadev->dev); -} - -static int cdadev_init(struct cda_dev *cdadev) -{ - // Create and initialize device structures - int ret = -ENOMEM; - struct device *dev = &cdadev->dev; - device_initialize(dev); - - dev->class = &cda_class; - dev->parent = &cdadev->pcidev->dev; - - cdadev->dummy_blk = kzalloc(sizeof(*cdadev->dummy_blk), in_atomic() ? GFP_ATOMIC : GFP_KERNEL); - if (!cdadev->dummy_blk) { - dev_err(&cdadev->pcidev->dev, "Can't alloc dummy blk\n"); - goto alloc_dummy; - } - idr_init(&cdadev->mblk_idr); - ret = ida_simple_get(&cdaminor_ida, 0, CDA_DEV_MINOR_MAX, GFP_KERNEL); - if( ret < 0 ) - goto err_minor_get; - - cdadev->minor = ret; - dev->devt = MKDEV(MAJOR(cdadev_first), cdadev->minor); - ret = dev_set_name(dev, "cda%02d", cdadev->minor); - if (ret) - goto err_set_name; - - ret = device_add(dev); - if (ret) { - dev_err(&cdadev->pcidev->dev, "Unable to create device. Error 0x%x\n", ret); - goto err_device_add; - } - - INIT_LIST_HEAD(&cdadev->mem_maps); - INIT_LIST_HEAD(&cdadev->mem_blocks); - spin_lock_init(&cdadev->mblk_sl); - - mutex_init(&cdadev->ilock); - cdadev->ints = NULL; - - return 0; -err_device_add: -err_set_name: - ida_simple_remove(&cdaminor_ida, cdadev->minor); -err_minor_get: -alloc_dummy: - put_device(dev); - return ret; -} - -static int cda_pci_init(struct pci_dev *pcidev) -{ - // PCI initialization - int ret; - ret = pci_enable_device_mem(pcidev); - if( ret ) { - dev_err(&pcidev->dev, "Cannot enable PCI device mem\n"); - goto err_en_devmem; - } - - if( dma_set_mask_and_coherent(&pcidev->dev, DMA_BIT_MASK(64)) && - (ret = dma_set_mask_and_coherent(&pcidev->dev, DMA_BIT_MASK(32))) ) { - dev_err(&pcidev->dev, "Set DMA mask 32/64 failed: 0x%x\n", ret); - goto err_dma_set_mask; - } - - ret = pci_request_regions(pcidev, cda_name); - if( ret ) { - dev_err(&pcidev->dev, "Fail request regions: 0x%x\n", ret); - goto err_req_regions; - } - - pci_set_master(pcidev); - return 0; -err_req_regions: -err_dma_set_mask: - pci_disable_device(pcidev); -err_en_devmem: - return ret; -} - -static int cda_cdev_init(struct cda_dev *cdadev) -{ - int ret; - struct cdev *cdev = &cdadev->cdev; - - cdev_init(cdev, &cda_fileops); - cdev->owner = THIS_MODULE; - kobject_set_name(&cdev->kobj, "%s%d", cda_name, cdadev->minor); - ret = cdev_add(cdev, MKDEV(MAJOR(cdadev_first), cdadev->minor), CDA_DEV_MINOR_MAX); - if (ret) - return ret; - return 0; -} - -static int cda_pci_probe(struct pci_dev *pcidev, - const struct pci_device_id *id) -{ - int ret; - struct cda_dev *cdadev = kzalloc(sizeof(*cdadev), in_atomic() ? GFP_ATOMIC : GFP_KERNEL); - if (!cdadev) { - return -ENOMEM; - } - - cdadev->pcidev = pcidev; - ret = cdadev_init(cdadev); - if( ret ) - goto err_cdadev_init; - - ret = cda_pci_init(pcidev); - if( ret ) - goto err_pci_init; - - ret = cda_mems_create(cdadev); - if( ret ) - goto err_sysfsmem; - - ret = cda_open_bars(cdadev); - if( ret ) - goto err_check_bar; - - ret = cda_cdev_init(cdadev); - if( ret ) - goto err_cdev_init; - - spin_lock(&cdadevlist_sl); - list_add(&cdadev->devices, &cdadevs); - spin_unlock(&cdadevlist_sl); - - pci_set_drvdata(pcidev, cdadev); - return 0; -err_cdev_init: - cda_release_bars(cdadev); -err_check_bar: - cda_mems_release(cdadev); -err_sysfsmem: - pci_release_regions(pcidev); - pci_disable_device(pcidev); -err_pci_init: - cdadev_free(cdadev); -err_cdadev_init: - return ret; -} - -static void cda_pci_remove(struct pci_dev *pcidev) -{ - struct cda_dev *cdadev = pci_get_drvdata(pcidev); - - if (!cdadev) - return; - - spin_lock(&cdadevlist_sl); - list_del(&cdadev->devices); - spin_unlock(&cdadevlist_sl); - - cdev_del(&cdadev->cdev); - cda_release_bars(cdadev); - - cda_mems_release(cdadev); - - cda_free_irqs(cdadev, NULL); - cda_unmmap_dev_mem(cdadev, NULL); - cda_free_dev_mem(cdadev, NULL); - - pci_release_regions(pcidev); - pci_disable_device(pcidev); - - cdadev_free(cdadev); -} - -static int cda_cdev_open(struct inode *ino, struct file *file) -{ - int ret; - struct cda_dev *cdadev = - container_of(ino->i_cdev, - struct cda_dev, - cdev); - if (!cdadev) { - ret = -ENODEV; - goto out; - } - get_device(&cdadev->dev); - file->private_data = cdadev; - return nonseekable_open(ino, file); -out: - return ret; -} - -static int cda_cdev_release(struct inode *ino, struct file *file) -{ - struct cda_dev *cdadev = file->private_data; - if (!cdadev) - return -ENODEV; - - cda_cancel_req(cdadev, (void *)file); - cda_free_irqs(cdadev, (void *)file); - cda_unmmap_dev_mem(cdadev, (void *)file); - cda_free_dev_mem(cdadev, (void *)file); - cda_sem_rel_by_owner(cdadev, (void *)file); - - put_device(&cdadev->dev); - return 0; -} - -static long cda_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - struct cda_dev *cdadev = file->private_data; - switch (cmd) { - - case CDA_ALLOC_MEM: - return cda_alloc_mem(cdadev, (void *)file, (void __user *)arg); - - case CDA_FREE_MEM: - return cda_free_mem_by_idx(cdadev, (void *)file, (void __user *)arg); - - case CDA_MAP_MEM: - return cda_map_mem(cdadev, (void *)file, (void __user *)arg); - - case CDA_UNMAP_MEM: - return cda_unmap_mem_by_idx(cdadev, (void *)file, (void __user *)arg); - - case CDA_INIT_INT: - return cda_init_interrupts(cdadev, (void *)file, (void __user *)arg); - - case CDA_FREE_INT: - return cda_free_irqs(cdadev, (void *)file); - - case CDA_REQ_INT: - return cda_req_int(cdadev, (void *)file, (void __user *) arg); - - case CDA_INT_CANCEL: - return cda_cancel_req(cdadev, (void *)file); - - case CDA_SEM_AQ: - return cda_sem_aq(cdadev, (void *)file, (void __user *) arg); - - case CDA_SEM_REL: - return cda_sem_rel(cdadev, (void *)file, (void __user *) arg); - - default: - return -ENOTTY; - } -} - -static void cdadev_release(struct device *dev) -{ - struct cda_dev *cdadev = container_of(dev, struct cda_dev, dev); - kfree(cdadev); -} - -static int __init cdadrv_init(void) -{ - int ret; - size_t pci_id_table_size = ARRAY_SIZE(cda_pci_ids); - if( test_probe ) { - printk("Test run. Nothing initialized\n"); - return 0; - } - - ret = alloc_chrdev_region(&cdadev_first, 0, CDA_DEV_MINOR_MAX, cda_name); - if( ret ) - goto err_alloc_cdev_reg; - - ret = class_register(&cda_class); - if( ret ) - goto err_cls_reg; - - if( (req_pci_did || req_pci_vid) && pci_id_table_size >= 2 ) { - // Last table element is 0,0 - // Update pre-last item - cda_pci_ids[pci_id_table_size-2].vendor = req_pci_vid; - cda_pci_ids[pci_id_table_size-2].device = req_pci_did; - } - ret = pci_register_driver(&cda_pci); - if( ret ) - goto err_pci_reg_drv; - - return 0; - -err_pci_reg_drv: - class_unregister(&cda_class); -err_cls_reg: - unregister_chrdev_region(cdadev_first, CDA_DEV_MINOR_MAX); -err_alloc_cdev_reg: - return ret; -} - -static void __exit dcadrv_exit(void) -{ - if( test_probe ) { - printk("Stop test run. Nothing initialized\n"); - return; - } - pci_unregister_driver(&cda_pci); - class_unregister(&cda_class); - unregister_chrdev_region(cdadev_first, CDA_DEV_MINOR_MAX); -} - -module_init(cdadrv_init); -module_exit(dcadrv_exit); +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2020 DeGirum Corp., Egor Pomozov. +// +// CDA linux driver mem blocks/mem maps and interrupt request handler +// +// This program is free software; you can redistribute it and/or modify it +// under the terms and conditions of the GNU General Public License, +// version 2, as published by the Free Software Foundation. +// +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/fs.h> + +#include "cdadrv.h" +#include "cdaioctl.h" + +MODULE_AUTHOR("DeGirum Corp., Egor Pomozov"); +MODULE_DESCRIPTION("CDA linux driver to access pci devices"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.5.0.3"); +// The version has to be in the format n.n.n.n, where each n is a single digit + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) +#error Too old kernel +#endif + +static dev_t cdadev_first; +static const char cda_name[] = "cda"; +static int req_pci_did; +static int req_pci_vid; +static int test_probe; + +#define CDA_DEV_MINOR_MAX 32 +static DEFINE_SPINLOCK(cdadevlist_sl); +static DEFINE_IDA(cdaminor_ida); +static LIST_HEAD(cdadevs); + +// Module parameters +module_param_named(did, req_pci_did, int, 0644); +MODULE_PARM_DESC(did, "Set required PCI device ID"); +module_param_named(vid, req_pci_vid, int, 0644); +MODULE_PARM_DESC(vid, "Set required PCI vendor ID"); +module_param_named(test_probe, test_probe, int, 0644); +MODULE_PARM_DESC(test_probe, "Check permissions to load driver"); + +static void cdadev_release(struct device *kdev); +static void cda_pci_remove(struct pci_dev *pcidev); +static int cda_pci_probe(struct pci_dev *pcidev, + const struct pci_device_id *id); + +static int cda_cdev_open(struct inode *ino, struct file *file); +static int cda_cdev_release(struct inode *ino, struct file *file); +static long cda_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg); + +static struct pci_device_id cda_pci_ids[] = { + { PCI_DEVICE(0x1f0d, 0x0100) }, + { PCI_DEVICE(0x1f0d, 0x8101) }, + { PCI_DEVICE(0x1f0d, 0x0101) }, + { PCI_DEVICE(0x10ee, 0x8011) }, + { PCI_DEVICE(0, 0) }, + { PCI_DEVICE(0, 0) }, +}; + +static struct pci_driver cda_pci = { + .name = cda_name, + .probe = cda_pci_probe, + .remove = cda_pci_remove, + .id_table = cda_pci_ids, +}; + +static const struct file_operations cda_fileops = { + .owner = THIS_MODULE, + .open = cda_cdev_open, + .release = cda_cdev_release, + .unlocked_ioctl = cda_cdev_ioctl, +}; + +static struct class cda_class = { + .name = cda_name, + .dev_release = cdadev_release, +}; +/* +static inline bool cda_kernel_is_locked_down(void) +{ +#ifdef CONFIG_LOCK_DOWN_KERNEL +#ifdef CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT / * fedora * / + return kernel_is_locked_down(NULL); +#elif CONFIG_EFI_SECURE_BOOT_LOCK_DOWN / * ubuntu * / + return kernel_is_locked_down(); +#else + return false; +#endif +#else + return false; +#endif +} +*/ +static void cdadev_free(struct cda_dev *cdadev) +{ + ida_simple_remove(&cdaminor_ida, cdadev->minor); + device_del(&cdadev->dev); + put_device(&cdadev->dev); +} + +static int cdadev_init(struct cda_dev *cdadev) +{ + // Create and initialize device structures + int ret = -ENOMEM; + struct device *dev = &cdadev->dev; + + device_initialize(dev); + + dev->class = &cda_class; + dev->parent = &cdadev->pcidev->dev; + + cdadev->dummy_blk = kzalloc(sizeof(*cdadev->dummy_blk), GFP_KERNEL); + if (!cdadev->dummy_blk) + goto alloc_dummy; + idr_init(&cdadev->mblk_idr); + ret = ida_simple_get(&cdaminor_ida, 0, CDA_DEV_MINOR_MAX, GFP_KERNEL); + if (ret < 0) + goto err_minor_get; + + cdadev->minor = ret; + dev->devt = MKDEV(MAJOR(cdadev_first), cdadev->minor); + ret = dev_set_name(dev, "cda%02d", cdadev->minor); + if (ret) + goto err_set_name; + + ret = device_add(dev); + if (ret) { + dev_err(&cdadev->pcidev->dev, "Unable to create device. Error 0x%x\n", ret); + goto err_device_add; + } + + INIT_LIST_HEAD(&cdadev->mem_maps); + INIT_LIST_HEAD(&cdadev->mem_blocks); + spin_lock_init(&cdadev->mblk_sl); + + mutex_init(&cdadev->ilock); + cdadev->ints = NULL; + + return 0; +err_device_add: +err_set_name: + ida_simple_remove(&cdaminor_ida, cdadev->minor); +err_minor_get: +alloc_dummy: + put_device(dev); + return ret; +} + +static int cda_pci_init(struct pci_dev *pcidev) +{ + // PCI initialization + int ret; + + ret = pci_enable_device_mem(pcidev); + if (ret) { + dev_err(&pcidev->dev, "Cannot enable PCI device mem\n"); + goto err_en_devmem; + } + + ret = dma_set_mask_and_coherent(&pcidev->dev, DMA_BIT_MASK(64)); + if (ret) { + ret = dma_set_mask_and_coherent(&pcidev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pcidev->dev, "Set DMA mask 32/64 failed: 0x%x\n", ret); + goto err_dma_set_mask; + } + } + + ret = pci_request_regions(pcidev, cda_name); + if (ret) { + dev_err(&pcidev->dev, "Fail request regions: 0x%x\n", ret); + goto err_req_regions; + } + + pci_set_master(pcidev); + return 0; +err_req_regions: +err_dma_set_mask: + pci_disable_device(pcidev); +err_en_devmem: + return ret; +} + +static int cda_cdev_init(struct cda_dev *cdadev) +{ + int ret; + struct cdev *cdev = &cdadev->cdev; + + cdev_init(cdev, &cda_fileops); + cdev->owner = THIS_MODULE; + kobject_set_name(&cdev->kobj, "%s%d", cda_name, cdadev->minor); + ret = cdev_add(cdev, MKDEV(MAJOR(cdadev_first), cdadev->minor), CDA_DEV_MINOR_MAX); + if (ret) + return ret; + return 0; +} + +static int cda_pci_probe(struct pci_dev *pcidev, + const struct pci_device_id *id) +{ + int ret; + struct cda_dev *cdadev = kzalloc(sizeof(*cdadev), GFP_KERNEL); + + if (!cdadev) + return -ENOMEM; + + cdadev->pcidev = pcidev; + ret = cdadev_init(cdadev); + if (ret) + goto err_cdadev_init; + + ret = cda_pci_init(pcidev); + if (ret) + goto err_pci_init; + + ret = cda_mems_create(cdadev); + if (ret) + goto err_sysfsmem; + + ret = cda_open_bars(cdadev); + if (ret) + goto err_check_bar; + + ret = cda_cdev_init(cdadev); + if (ret) + goto err_cdev_init; + + spin_lock(&cdadevlist_sl); + list_add(&cdadev->devices, &cdadevs); + spin_unlock(&cdadevlist_sl); + + pci_set_drvdata(pcidev, cdadev); + return 0; +err_cdev_init: + cda_release_bars(cdadev); +err_check_bar: + cda_mems_release(cdadev); +err_sysfsmem: + pci_release_regions(pcidev); + pci_disable_device(pcidev); +err_pci_init: + cdadev_free(cdadev); +err_cdadev_init: + return ret; +} + +static void cda_pci_remove(struct pci_dev *pcidev) +{ + struct cda_dev *cdadev = pci_get_drvdata(pcidev); + + if (!cdadev) + return; + + spin_lock(&cdadevlist_sl); + list_del(&cdadev->devices); + spin_unlock(&cdadevlist_sl); + + cdev_del(&cdadev->cdev); + cda_release_bars(cdadev); + + cda_mems_release(cdadev); + + cda_free_irqs(cdadev, NULL); + cda_unmmap_dev_mem(cdadev, NULL); + cda_free_dev_mem(cdadev, NULL); + + pci_release_regions(pcidev); + pci_disable_device(pcidev); + + cdadev_free(cdadev); +} + +static int cda_cdev_open(struct inode *ino, struct file *file) +{ + int ret; + struct cda_dev *cdadev = + container_of(ino->i_cdev, + struct cda_dev, + cdev); + if (!cdadev) { + ret = -ENODEV; + goto out; + } + get_device(&cdadev->dev); + file->private_data = cdadev; + return nonseekable_open(ino, file); +out: + return ret; +} + +static int cda_cdev_release(struct inode *ino, struct file *file) +{ + struct cda_dev *cdadev = file->private_data; + + if (!cdadev) + return -ENODEV; + + cda_cancel_req(cdadev, (void *)file); + cda_free_irqs(cdadev, (void *)file); + cda_unmmap_dev_mem(cdadev, (void *)file); + cda_free_dev_mem(cdadev, (void *)file); + cda_sem_rel_by_owner(cdadev, (void *)file); + + put_device(&cdadev->dev); + return 0; +} + +static long cda_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct cda_dev *cdadev = file->private_data; + + switch (cmd) { + case CDA_ALLOC_MEM: + return cda_alloc_mem(cdadev, (void *)file, (void __user *)arg); + + case CDA_FREE_MEM: + return cda_free_mem_by_idx(cdadev, (void *)file, (void __user *)arg); + + case CDA_MAP_MEM: + return cda_map_mem(cdadev, (void *)file, (void __user *)arg); + + case CDA_UNMAP_MEM: + return cda_unmap_mem_by_idx(cdadev, (void *)file, (void __user *)arg); + + case CDA_INIT_INT: + return cda_init_interrupts(cdadev, (void *)file, (void __user *)arg); + + case CDA_FREE_INT: + return cda_free_irqs(cdadev, (void *)file); + + case CDA_REQ_INT: + return cda_req_int(cdadev, (void *)file, (void __user *) arg); + + case CDA_INT_CANCEL: + return cda_cancel_req(cdadev, (void *)file); + + case CDA_SEM_AQ: + return cda_sem_aq(cdadev, (void *)file, (void __user *) arg); + + case CDA_SEM_REL: + return cda_sem_rel(cdadev, (void *)file, (void __user *) arg); + + default: + return -ENOTTY; + } +} + +static void cdadev_release(struct device *dev) +{ + struct cda_dev *cdadev = container_of(dev, struct cda_dev, dev); + + kfree(cdadev); +} + +static int __init cdadrv_init(void) +{ + int ret; + size_t pci_id_table_size = ARRAY_SIZE(cda_pci_ids); + + if (test_probe) { + printk("Test run. Nothing initialized\n"); + return 0; + } + + ret = alloc_chrdev_region(&cdadev_first, 0, CDA_DEV_MINOR_MAX, cda_name); + if (ret) + goto err_alloc_cdev_reg; + + ret = class_register(&cda_class); + if (ret) + goto err_cls_reg; + + if ((req_pci_did || req_pci_vid) && pci_id_table_size >= 2) { + // Last table element is 0,0 + // Update pre-last item + cda_pci_ids[pci_id_table_size-2].vendor = req_pci_vid; + cda_pci_ids[pci_id_table_size-2].device = req_pci_did; + } + ret = pci_register_driver(&cda_pci); + if (ret) + goto err_pci_reg_drv; + + return 0; + +err_pci_reg_drv: + class_unregister(&cda_class); +err_cls_reg: + unregister_chrdev_region(cdadev_first, CDA_DEV_MINOR_MAX); +err_alloc_cdev_reg: + return ret; +} + +static void __exit dcadrv_exit(void) +{ + if (test_probe) { + printk("Stop test run. Nothing initialized\n"); + return; + } + pci_unregister_driver(&cda_pci); + class_unregister(&cda_class); + unregister_chrdev_region(cdadev_first, CDA_DEV_MINOR_MAX); +} + +module_init(cdadrv_init); +module_exit(dcadrv_exit); diff --git a/src/cdadrv.h b/src/cdadrv.h index 3a8ca2d..67a7e2d 100644 --- a/src/cdadrv.h +++ b/src/cdadrv.h @@ -1,71 +1,71 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright(c) 2020 DeGirum Corp., Egor Pomozov. -// -// CDA linux driver mem blocks/mem maps and interrupt request handler -// -// This program is free software; you can redistribute it and/or modify it -// under the terms and conditions of the GNU General Public License, -// version 2, as published by the Free Software Foundation. -// -#include <linux/pci.h> -#include <linux/cdev.h> -#include <linux/version.h> - - -#define CDA_MAX_DRV_SEMAPHORES (16) - -struct cda_interrupts; -struct cda_bar; -struct cda_dev; -// Dummy block for fast releasing -struct cda_dummy_blk { - struct cda_dev *dev; - int index; -}; - -struct cda_dev { - struct cdev cdev; - struct device dev; - - int minor; - struct list_head devices; - - struct pci_dev *pcidev; - unsigned long stored_flags[PCI_ROM_RESOURCE]; - - struct mutex ilock; - struct cda_interrupts *ints; - - struct kobject *kobj_mems; - struct cda_dummy_blk *dummy_blk; - struct idr mblk_idr; - spinlock_t mblk_sl; - struct list_head mem_blocks; - struct list_head mem_maps; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) - // Security kernel lock needs w/a to access BARs - struct kobject *kobj_bars; - struct cda_bar *sysfs_bar[PCI_ROM_RESOURCE]; // 6 BARs excl. ROM -#endif - u64 semaphores[CDA_MAX_DRV_SEMAPHORES]; - void *sem_owner[CDA_MAX_DRV_SEMAPHORES]; -}; - -int cda_alloc_mem(struct cda_dev *dev, void *owner, void __user *arg); -int cda_free_mem_by_idx(struct cda_dev *dev, void *owner, void __user *ureq); -int cda_map_mem(struct cda_dev *dev, void *owner, void __user *arg); -int cda_unmap_mem_by_idx(struct cda_dev *dev, void *owner, void __user *ureq); -void cda_unmmap_dev_mem(struct cda_dev *dev, void *owner); -void cda_free_dev_mem(struct cda_dev *dev, void *owner); -int cda_init_interrupts(struct cda_dev *dev, void *owner, void __user *ureq); -int cda_mems_create(struct cda_dev *dev); -int cda_free_irqs(struct cda_dev *dev, void *owner); -void cda_mems_release(struct cda_dev *dev); -int cda_req_int(struct cda_dev *dev, void *owner, void __user *ureq); -int cda_cancel_req(struct cda_dev *dev, void *owner); -int cda_open_bars(struct cda_dev *cdadev); -void cda_release_bars(struct cda_dev *cdadev); -int cda_sem_aq(struct cda_dev *cdadev, void *owner, void __user *ureq); -int cda_sem_rel(struct cda_dev *cdadev, void *owner, void __user *ureq); -void cda_sem_rel_by_owner(struct cda_dev *dev, void *owner); +/* SPDX-License-Identifier: GPL-2.0 */ +// Copyright(c) 2020 DeGirum Corp., Egor Pomozov. +// +// CDA linux driver mem blocks/mem maps and interrupt request handler +// +// This program is free software; you can redistribute it and/or modify it +// under the terms and conditions of the GNU General Public License, +// version 2, as published by the Free Software Foundation. +// +#include <linux/pci.h> +#include <linux/cdev.h> +#include <linux/version.h> + + +#define CDA_MAX_DRV_SEMAPHORES (16) + +struct cda_interrupts; +struct cda_bar; +struct cda_dev; +// Dummy block for fast releasing +struct cda_dummy_blk { + struct cda_dev *dev; + int index; +}; + +struct cda_dev { + struct cdev cdev; + struct device dev; + + int minor; + struct list_head devices; + + struct pci_dev *pcidev; + unsigned long stored_flags[PCI_ROM_RESOURCE]; + + struct mutex ilock; + struct cda_interrupts *ints; + + struct kobject *kobj_mems; + struct cda_dummy_blk *dummy_blk; + struct idr mblk_idr; + spinlock_t mblk_sl; + struct list_head mem_blocks; + struct list_head mem_maps; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) + // Security kernel lock needs w/a to access BARs + struct kobject *kobj_bars; + struct cda_bar *sysfs_bar[PCI_ROM_RESOURCE]; // 6 BARs excl. ROM +#endif + u64 semaphores[CDA_MAX_DRV_SEMAPHORES]; + void *sem_owner[CDA_MAX_DRV_SEMAPHORES]; +}; + +int cda_alloc_mem(struct cda_dev *dev, void *owner, void __user *arg); +int cda_free_mem_by_idx(struct cda_dev *dev, void *owner, void __user *ureq); +int cda_map_mem(struct cda_dev *dev, void *owner, void __user *arg); +int cda_unmap_mem_by_idx(struct cda_dev *dev, void *owner, void __user *ureq); +void cda_unmmap_dev_mem(struct cda_dev *dev, void *owner); +void cda_free_dev_mem(struct cda_dev *dev, void *owner); +int cda_init_interrupts(struct cda_dev *dev, void *owner, void __user *ureq); +int cda_mems_create(struct cda_dev *dev); +int cda_free_irqs(struct cda_dev *dev, void *owner); +void cda_mems_release(struct cda_dev *dev); +int cda_req_int(struct cda_dev *dev, void *owner, void __user *ureq); +int cda_cancel_req(struct cda_dev *dev, void *owner); +int cda_open_bars(struct cda_dev *cdadev); +void cda_release_bars(struct cda_dev *cdadev); +int cda_sem_aq(struct cda_dev *cdadev, void *owner, void __user *ureq); +int cda_sem_rel(struct cda_dev *cdadev, void *owner, void __user *ureq); +void cda_sem_rel_by_owner(struct cda_dev *dev, void *owner); diff --git a/src/cdaioctl.h b/src/cdaioctl.h index bb10a9c..6d1cec4 100644 --- a/src/cdaioctl.h +++ b/src/cdaioctl.h @@ -1,62 +1,62 @@ -// SPDX-License-Identifier: LGPL-3.0 -// Copyright(c) 2020 DeGirum Corp., Egor Pomozov. -// -// CDA linux driver mem blocks/mem maps and interrupt request handler -// -// This program is free software; you can redistribute it and/or modify it -// under the terms and conditions of the Lesser GNU General Public License, -// version 3, as published by the Free Software Foundation. -// -#ifndef LINUX_VERSION_CODE -#include <stdint.h> // only include if we're not compiling against the kernel -#endif - -#define CDA_IOCTL_MAGIC 'C' -#define CDA_ALLOC_MEM _IOWR(CDA_IOCTL_MAGIC, 0x1, long) -#define CDA_FREE_MEM _IOW(CDA_IOCTL_MAGIC, 0x2, long) -#define CDA_MAP_MEM _IOWR(CDA_IOCTL_MAGIC, 0x3, long) -#define CDA_UNMAP_MEM _IOW(CDA_IOCTL_MAGIC, 0x4, long) -#define CDA_INIT_INT _IOWR(CDA_IOCTL_MAGIC, 0x5, long) -#define CDA_FREE_INT _IOW(CDA_IOCTL_MAGIC, 0x6, long) -#define CDA_REQ_INT _IOWR(CDA_IOCTL_MAGIC, 0x7, long) -#define CDA_INT_CANCEL _IOWR(CDA_IOCTL_MAGIC, 0x8, long) -#define CDA_SEM_AQ _IOW(CDA_IOCTL_MAGIC, 0x9, long) -#define CDA_SEM_REL _IOW(CDA_IOCTL_MAGIC, 0xA, long) - -struct cda_alloc_mem { - uint32_t size; - uint32_t index; -}; - -struct cda_map_mem { - uintptr_t vaddr; - uint32_t size; - uint32_t index; -}; - -struct cda_drv_sg_item { - uint64_t paddr; - uint32_t size; -}; - -struct cda_req_int { - uint32_t vector; - uint64_t timeout; - uint32_t reset; -}; - -enum int_type { - LEGACY_INTERRUPT = 0, - MSI = 1, - MSIX = 2 -}; - -struct cda_int_lock { - uint32_t inttype; - uint32_t vectors; -}; - -struct cda_sem_aq { - uint32_t sem_id; - uint64_t time_ns; -}; +/* SPDX-License-Identifier: LGPL-3.0 */ +// Copyright(c) 2020 DeGirum Corp., Egor Pomozov. +// +// CDA linux driver mem blocks/mem maps and interrupt request handler +// +// This program is free software; you can redistribute it and/or modify it +// under the terms and conditions of the Lesser GNU General Public License, +// version 3, as published by the Free Software Foundation. +// +#ifndef LINUX_VERSION_CODE +#include <stdint.h> // only include if we're not compiling against the kernel +#endif + +#define CDA_IOCTL_MAGIC 'C' +#define CDA_ALLOC_MEM _IOWR(CDA_IOCTL_MAGIC, 0x1, long) +#define CDA_FREE_MEM _IOW(CDA_IOCTL_MAGIC, 0x2, long) +#define CDA_MAP_MEM _IOWR(CDA_IOCTL_MAGIC, 0x3, long) +#define CDA_UNMAP_MEM _IOW(CDA_IOCTL_MAGIC, 0x4, long) +#define CDA_INIT_INT _IOWR(CDA_IOCTL_MAGIC, 0x5, long) +#define CDA_FREE_INT _IOW(CDA_IOCTL_MAGIC, 0x6, long) +#define CDA_REQ_INT _IOWR(CDA_IOCTL_MAGIC, 0x7, long) +#define CDA_INT_CANCEL _IOWR(CDA_IOCTL_MAGIC, 0x8, long) +#define CDA_SEM_AQ _IOW(CDA_IOCTL_MAGIC, 0x9, long) +#define CDA_SEM_REL _IOW(CDA_IOCTL_MAGIC, 0xA, long) + +struct cda_alloc_mem { + uint32_t size; + uint32_t index; +}; + +struct cda_map_mem { + uintptr_t vaddr; + uint32_t size; + uint32_t index; +}; + +struct cda_drv_sg_item { + uint64_t paddr; + uint32_t size; +}; + +struct cda_req_int { + uint32_t vector; + uint64_t timeout; + uint32_t reset; +}; + +enum int_type { + LEGACY_INTERRUPT = 0, + MSI = 1, + MSIX = 2 +}; + +struct cda_int_lock { + uint32_t inttype; + uint32_t vectors; +}; + +struct cda_sem_aq { + uint32_t sem_id; + uint64_t time_ns; +}; diff --git a/src/cdamem.c b/src/cdamem.c index 5280185..ed39573 100644 --- a/src/cdamem.c +++ b/src/cdamem.c @@ -1,861 +1,858 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright(c) 2020 Egor Pomozov. -// -// Originally memalloc sequence was designed for simple driver -// in Aquantia Corp by Vadim Solomin -// Later was updated by QA team in Aquantia Corp. -// Later it was additionally modifyied by Egor Pomozov -// -// CDA linux driver memory request handler -// -// This program is free software; you can redistribute it and/or modify it -// under the terms and conditions of the GNU General Public License, -// version 2, as published by the Free Software Foundation. -// - -#include <linux/fs.h> -#include <linux/uaccess.h> -#include <linux/mm.h> -#include <linux/dma-mapping.h> - -#include "cdadrv.h" -#include "cdaioctl.h" - -#include <linux/version.h> - - -#if LINUX_VERSION_CODE < KERNEL_VERSION(5,6,0) -/** - * pin_user_pages_fast() - pin user pages in memory without taking locks - * - * For now, this is a placeholder function, until various call sites are - * converted to use the correct get_user_pages*() or pin_user_pages*() API. So, - * this is identical to get_user_pages_fast(). - * - * This is intended for Case 1 (DIO) in Documentation/vm/pin_user_pages.rst. It - * is NOT intended for Case 2 (RDMA: long-term pins). - */ -static int pin_user_pages_fast(unsigned long start, int nr_pages, - unsigned int gup_flags, struct page **pages) -{ - /* - * This is a placeholder, until the pin functionality is activated. - * Until then, just behave like the corresponding get_user_pages*() - * routine. - */ - return get_user_pages_fast(start, nr_pages, gup_flags, pages); -} - -/** - * unpin_user_page() - release a gup-pinned page - * @page: pointer to page to be released - * - * Pages that were pinned via pin_user_pages*() must be released via either - * unpin_user_page(), or one of the unpin_user_pages*() routines. This is so - * that eventually such pages can be separately tracked and uniquely handled. In - * particular, interactions with RDMA and filesystems need special handling. - * - * unpin_user_page() and put_page() are not interchangeable, despite this early - * implementation that makes them look the same. unpin_user_page() calls must - * be perfectly matched up with pin*() calls. - */ -static inline void unpin_user_page(struct page *page) -{ - put_page(page); -} - -/** - * unpin_user_pages() - release an array of gup-pinned pages. - * @pages: array of pages to be marked dirty and released. - * @npages: number of pages in the @pages array. - * - * For each page in the @pages array, release the page using unpin_user_page(). - * - * Please see the unpin_user_page() documentation for details. - */ -static void unpin_user_pages(struct page **pages, unsigned long npages) -{ - unsigned long index; - - /* - * TODO: this can be optimized for huge pages: if a series of pages is - * physically contiguous and part of the same compound page, then a - * single operation to the head page should suffice. - */ - for (index = 0; index < npages; index++) - unpin_user_page(pages[index]); -} - -/** - * unpin_user_pages_dirty_lock() - release and optionally dirty gup-pinned pages - * @pages: array of pages to be maybe marked dirty, and definitely released. - * @npages: number of pages in the @pages array. - * @make_dirty: whether to mark the pages dirty - * - * "gup-pinned page" refers to a page that has had one of the get_user_pages() - * variants called on that page. - * - * For each page in the @pages array, make that page (or its head page, if a - * compound page) dirty, if @make_dirty is true, and if the page was previously - * listed as clean. In any case, releases all pages using unpin_user_page(), - * possibly via unpin_user_pages(), for the non-dirty case. - * - * Please see the unpin_user_page() documentation for details. - * - * set_page_dirty_lock() is used internally. If instead, set_page_dirty() is - * required, then the caller should a) verify that this is really correct, - * because _lock() is usually required, and b) hand code it: - * set_page_dirty_lock(), unpin_user_page(). - * - */ -static void unpin_user_pages_dirty_lock(struct page **pages, unsigned long npages, - bool make_dirty) -{ - unsigned long index; - - /* - * TODO: this can be optimized for huge pages: if a series of pages is - * physically contiguous and part of the same compound page, then a - * single operation to the head page should suffice. - */ - - if (!make_dirty) { - unpin_user_pages(pages, npages); - return; - } - - for (index = 0; index < npages; index++) { - struct page *page = compound_head(pages[index]); - /* - * Checking PageDirty at this point may race with - * clear_page_dirty_for_io(), but that's OK. Two key - * cases: - * - * 1) This code sees the page as already dirty, so it - * skips the call to set_page_dirty(). That could happen - * because clear_page_dirty_for_io() called - * page_mkclean(), followed by set_page_dirty(). - * However, now the page is going to get written back, - * which meets the original intention of setting it - * dirty, so all is well: clear_page_dirty_for_io() goes - * on to call TestClearPageDirty(), and write the page - * back. - * - * 2) This code sees the page as clean, so it calls - * set_page_dirty(). The page stays dirty, despite being - * written back, so it gets written back again in the - * next writeback cycle. This is harmless. - */ - if (!PageDirty(page)) - set_page_dirty_lock(page); - unpin_user_page(page); - } -} - -#endif - -static ssize_t name_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct cda_dev *cdadev = container_of((dev), struct cda_dev, dev); - return sprintf(buf, "cda%d\n", cdadev->minor); -} -static DEVICE_ATTR_RO(name); - -static struct attribute *cda_attrs[] = { - &dev_attr_name.attr, - NULL, -}; - -static struct attribute_group cda_attr_grp = { - .attrs = cda_attrs, -}; - -static ssize_t mblk_attr_show( - struct kobject *kobj, - struct attribute *attr, - char *buf); - -static void mblk_release(struct kobject *kobj); - -struct cda_mblk { - struct cda_dev *dev; - int index; - - struct kobject kobj; - uint32_t req_size; - void *vaddr; //kernel - uint32_t size; - dma_addr_t paddr; - void *owner; - struct list_head list; - struct bin_attribute mmap_attr; -}; - -struct cda_mmap { - struct cda_dev *dev; - int index; - - struct kobject kobj; - void *owner; - - void *vaddr; //original user - uint32_t size; //original user - uint32_t blk_cnt; - uint32_t mapped_blk_cnt; - uint32_t show_cnt; - struct sg_table sgt; - struct page **pages; - struct cda_drv_sg_item *sg_list; - struct list_head list; - struct bin_attribute mmap_attr; -}; - -struct mblkitem_sysfs_entry { - struct attribute attr; - ssize_t (*show)(struct cda_mblk *, char *); - ssize_t (*store)(struct cda_mblk *, char*, size_t); -}; - -#define cda_dev_mblk_attr(_field, _fmt) \ - static ssize_t \ - mblk_##_field##_show(struct cda_mblk *mblk, char *buf) \ - { \ - return sprintf(buf, _fmt, mblk->_field); \ - } \ - static struct mblkitem_sysfs_entry mblk_##_field##_attr = \ - __ATTR(_field, S_IRUGO, mblk_##_field##_show, NULL); - -#pragma GCC diagnostic ignored "-Wformat" -cda_dev_mblk_attr(vaddr, "0x%lx\n"); -cda_dev_mblk_attr(paddr, "0x%lx\n"); -cda_dev_mblk_attr(size, "0x%x\n"); -cda_dev_mblk_attr(req_size, "0x%x\n"); -cda_dev_mblk_attr(owner, "0x%p\n"); -cda_dev_mblk_attr(index, "%d\n"); -#pragma GCC diagnostic warning "-Wformat" - -static struct attribute *mblk_attrs[] = { - &mblk_vaddr_attr.attr, - &mblk_paddr_attr.attr, - &mblk_size_attr.attr, - &mblk_owner_attr.attr, - &mblk_req_size_attr.attr, - &mblk_index_attr.attr, - NULL, -}; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0) -ATTRIBUTE_GROUPS(mblk); -#endif -static const struct sysfs_ops mblk_ops = { - .show = mblk_attr_show, -}; - -struct kobj_type mblk_type = { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0) - .default_groups = mblk_groups, -#else - .default_attrs = mblk_attrs, -#endif - .sysfs_ops = &mblk_ops, - .release = mblk_release, -}; - -static ssize_t mblk_attr_show(struct kobject *kobj, - struct attribute *attr, char *buf) -{ - struct cda_mblk *mblk = container_of(kobj, struct cda_mblk, kobj); - struct mblkitem_sysfs_entry *entry = - container_of(attr, struct mblkitem_sysfs_entry, attr); - - if (!entry->show) - return -EIO; - - return entry->show(mblk, buf); -} - -static void mblk_release(struct kobject *kobj) -{ - struct cda_mblk *mblk = container_of(kobj, struct cda_mblk, kobj); - kfree(mblk); -} - -#define to_memmap(obj) container_of(obj, struct cda_mmap, kobj) - -struct memmapitem_sysfs_entry { - struct attribute attr; - ssize_t (*show)(struct cda_mmap *, char *); - ssize_t (*store)(struct cda_mmap *, char *, size_t); -}; - -#define cda_dev_memmap_attr(_field, _fmt) \ - static ssize_t \ - memmap_##_field##_show(struct cda_mmap *memmap, char *buf) \ - { \ - return sprintf(buf, _fmt, memmap->_field); \ - } \ - static struct memmapitem_sysfs_entry memmap_##_field##_attr = \ - __ATTR(_field, S_IRUGO, memmap_##_field##_show, NULL); - -#pragma GCC diagnostic ignored "-Wformat" -cda_dev_memmap_attr(owner, "0x%p\n"); -cda_dev_memmap_attr(vaddr, "0x%lx\n"); -cda_dev_memmap_attr(size, "0x%x\n"); -cda_dev_memmap_attr(index, "%d\n"); -cda_dev_memmap_attr(blk_cnt, "%d\n"); - -static ssize_t -memmap_sglist_show(struct cda_mmap *memmap, char *buf) -{ - const int sg_list_item_size = 16 + 8 + 2; //"%016llx %08lx\n" - int res = 0; - int i = memmap->show_cnt; - memmap->show_cnt = 0; - buf[0] = '\0'; - for( ; i < memmap->blk_cnt; i++ ) { - if( (res + sg_list_item_size) >= (PAGE_SIZE - 1)) /* https://lwn.net/Articles/178634/ */{ - memmap->show_cnt = i; - //printk("Split SG list. Next read starts with item: %d\n", i); - break; - } - res += sprintf(&buf[res], "%016llx %08lx\n", memmap->sg_list[i].paddr, memmap->sg_list[i].size); - } - return res; -} - -static struct memmapitem_sysfs_entry memmap_sglist_attr = - __ATTR(sglist, S_IRUGO, memmap_sglist_show, NULL); - -#pragma GCC diagnostic warning "-Wformat" -static struct attribute *memmap_attrs[] = { - &memmap_owner_attr.attr, - &memmap_vaddr_attr.attr, - &memmap_size_attr.attr, - &memmap_index_attr.attr, - &memmap_blk_cnt_attr.attr, - &memmap_sglist_attr.attr, - NULL, -}; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0) -ATTRIBUTE_GROUPS(memmap); -#endif - -static ssize_t memmap_attr_show(struct kobject *kobj, - struct attribute *attr, char *buf) -{ - struct cda_mmap *memmap = to_memmap(kobj); - struct memmapitem_sysfs_entry *entry = - container_of(attr, struct memmapitem_sysfs_entry, attr); - - if (!entry->show) - return -EIO; - - return entry->show(memmap, buf); -} - -static void memmap_release(struct kobject *kobj) -{ - struct cda_mmap *memmap = to_memmap(kobj); - kfree(memmap); -} - -static const struct sysfs_ops memmap_ops = { - .show = memmap_attr_show, -}; - -struct kobj_type memmap_type = { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0) - .default_groups = memmap_groups, -#else - .default_attrs = memmap_attrs, -#endif - .sysfs_ops = &memmap_ops, - .release = memmap_release, -}; - -static int mblk_mmap( struct file *file, - struct kobject *kobj, - struct bin_attribute *attr, - struct vm_area_struct *vma) -{ - struct cda_mblk *mblk = attr->private; - unsigned long requested = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; - unsigned long pages = (unsigned long)mblk->req_size >> PAGE_SHIFT; - - if (vma->vm_pgoff + requested > pages) - return -EINVAL; - - if( dma_mmap_coherent( &mblk->dev->pcidev->dev, - vma, - mblk->vaddr, - mblk->paddr, - mblk->req_size) ) - { - dev_err(&mblk->dev->pcidev->dev, "DMA remapping failed"); - return -ENXIO; - } - return 0; -} - -void cda_hide_memmap(struct cda_mmap *memmap); -int cda_publish_memmap(struct cda_mmap *memmap); -void cda_hide_mblk(struct cda_mblk *mblk); -int cda_publish_mblk(struct cda_mblk *mblk); - -int cda_publish_mblk(struct cda_mblk *mblk) -{ - int ret; - struct bin_attribute *mmap_attr = &mblk->mmap_attr; - - ret = kobject_add( &mblk->kobj, mblk->dev->kobj_mems, - "%04d", mblk->index); - if (ret) - goto err_add; - - mmap_attr->mmap = mblk_mmap; - mmap_attr->attr.name = "mmap"; - mmap_attr->attr.mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - mmap_attr->size = mblk->req_size; - mmap_attr->private = mblk; - ret = sysfs_create_bin_file(&mblk->kobj, mmap_attr); - if (ret) - goto err_map_add; - - return 0; - -err_map_add: - kobject_del(&mblk->kobj); -err_add: - kobject_put(&mblk->kobj); - return ret; -} - - -void cda_hide_mblk(struct cda_mblk *mblk) -{ - sysfs_remove_bin_file(&mblk->kobj, &mblk->mmap_attr); - kobject_del(&mblk->kobj); -} - -int cda_publish_memmap(struct cda_mmap *memmap) -{ - int ret; - struct bin_attribute *mmap_attr = &memmap->mmap_attr; - - ret = kobject_add( &memmap->kobj, memmap->dev->kobj_mems, - "%04d", memmap->index); - if (ret) - goto err_add; - - mmap_attr->attr.name = "memmapobj"; - mmap_attr->attr.mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - mmap_attr->size = memmap->size; - mmap_attr->private = memmap; - ret = sysfs_create_bin_file(&memmap->kobj, mmap_attr); - if (ret) - goto err_map_add; - - return 0; - -err_map_add: - kobject_del(&memmap->kobj); -err_add: - kobject_put(&memmap->kobj); - return ret; -} - -void cda_hide_memmap(struct cda_mmap *memmap) -{ - sysfs_remove_bin_file(&memmap->kobj, &memmap->mmap_attr); - kobject_del(&memmap->kobj); -} - -int cda_alloc_mem(struct cda_dev *dev, void *owner, void __user *ureq) -{ - int ret = -ENOMEM; - int idx; - struct cda_mblk *mblk; - struct cda_alloc_mem req; - if (copy_from_user(&req, ureq, sizeof(req))) - return -EFAULT; - - mblk = kzalloc(sizeof(*mblk), in_atomic() ? GFP_ATOMIC : GFP_KERNEL); - if (!mblk) { - dev_err(&dev->dev, "Can't alloc mblk\n"); - goto out; - } - INIT_LIST_HEAD(&mblk->list); - mblk->dev = dev; - kobject_init(&mblk->kobj, &mblk_type); - mblk->owner = owner; - mblk->size = req.size; - req.size = ALIGN(req.size, PAGE_SIZE); - - idr_preload(in_atomic() ? GFP_ATOMIC : GFP_KERNEL); - spin_lock(&dev->mblk_sl); - ret = idr_alloc(&dev->mblk_idr, mblk, - 1L, 0, in_atomic() ? GFP_ATOMIC : GFP_KERNEL); - spin_unlock(&dev->mblk_sl); - idr_preload_end(); - if (ret < 0) - goto err_idr; - mblk->index = req.index = idx = ret; - - mblk->vaddr = dma_alloc_coherent( - &dev->pcidev->dev, - req.size, - &mblk->paddr, - in_atomic() ? GFP_ATOMIC | GFP_KERNEL : GFP_KERNEL); - if (!mblk->vaddr) { - dev_err(&dev->dev, "Can't alloc DMA memory (size %u)", req.size); - ret = -1; - goto err_dma_alloc; - } - mblk->req_size = req.size; - - ret = cda_publish_mblk(mblk); - if (ret) { - dev_err(&dev->dev, "Can't publish mblk to sysfs: %d", ret); - goto err_publish; - } - - if(copy_to_user(ureq, &req, sizeof(req))) { - ret = -EFAULT; - goto err_copy_to_user; - } - - spin_lock(&dev->mblk_sl); - list_add(&mblk->list, &dev->mem_blocks); - spin_unlock(&dev->mblk_sl); - - return 0; - -err_copy_to_user: - cda_hide_mblk(mblk); -err_publish: - dma_free_coherent(&dev->pcidev->dev, mblk->req_size, - mblk->vaddr, mblk->paddr); -err_dma_alloc: - spin_lock(&dev->mblk_sl); - idr_remove(&dev->mblk_idr, idx); - spin_unlock(&dev->mblk_sl); -err_idr: - kobject_put(&mblk->kobj); -out: - return ret; -} - -static void cda_free_mem(struct cda_mblk *mblk) -{ - cda_hide_mblk(mblk); - dma_free_coherent(&mblk->dev->pcidev->dev, mblk->req_size, - mblk->vaddr, mblk->paddr); - kobject_put(&mblk->kobj); -} - -int cda_free_mem_by_idx(struct cda_dev *dev, void *owner, void __user *ureq) -{ - int memidx; - struct cda_mblk *mblk; - if (copy_from_user(&memidx, (void __user *)ureq, sizeof(memidx))) { - return -EFAULT; - } - - spin_lock(&dev->mblk_sl); - mblk = idr_find(&dev->mblk_idr, memidx); - if (mblk && mblk->index == memidx) { - if( mblk->owner != owner ) { - dev_warn(&dev->dev, "Free mblk from another owner\n"); - idr_replace(&dev->mblk_idr, dev->dummy_blk, memidx); - } - list_del(&mblk->list); - } else if(mblk) { - dev_warn(&dev->dev, "Free mblk with index %d, required %d\n", mblk->index, memidx); - } - spin_unlock(&dev->mblk_sl); - if (!mblk) - return -ENOENT; - if (mblk->index) { - cda_free_mem(mblk); - spin_lock(&dev->mblk_sl); - idr_remove(&dev->mblk_idr, mblk->index); - spin_unlock(&dev->mblk_sl); - } - return 0; -} - -void cda_free_dev_mem(struct cda_dev *dev, void *owner) -{ - struct cda_mblk *mblk, *tmp; - LIST_HEAD(mblks); - - spin_lock(&dev->mblk_sl); - if( owner == NULL ){ - idr_destroy(&dev->mblk_idr); - list_replace_init(&dev->mem_blocks, &mblks); - } else { - list_for_each_entry_safe(mblk, tmp, &dev->mem_blocks, list) { - if( mblk->index > 0L && mblk->owner == owner ) { - list_move(&mblk->list, &mblks); - idr_replace(&dev->mblk_idr, dev->dummy_blk, mblk->index); - } - } - } - spin_unlock(&dev->mblk_sl); - list_for_each_entry_safe(mblk, tmp, &mblks, list) { - // Unmap blocks owned by specified owner or all if owner is NULL - cda_free_mem(mblk); - if( owner != NULL ){ - spin_lock(&dev->mblk_sl); - idr_remove(&dev->mblk_idr, mblk->index); - spin_unlock(&dev->mblk_sl); - } - } -} - -static void cda_release_map(struct cda_mmap *memmap) -{ - dma_unmap_sg(memmap->dev->pcidev == NULL ? NULL : &memmap->dev->pcidev->dev, memmap->sgt.sgl, memmap->sgt.orig_nents, DMA_BIDIRECTIONAL); - unpin_user_pages_dirty_lock(memmap->pages, memmap->blk_cnt, 1); - memmap->mapped_blk_cnt = 0; -} - -static int cda_perform_mapping( - struct cda_mmap *memmap) -{ - uint i; - int nents; - struct scatterlist *sg; - ulong len = memmap->size; - void __user *buf = memmap->vaddr; - struct cda_drv_sg_item *cda_sg_list = memmap->sg_list; - sg = memmap->sgt.sgl; - for (i = 0; i < memmap->sgt.orig_nents; i++, sg = sg_next(sg)) { - unsigned int offset = offset_in_page(buf); - unsigned int nbytes = - min_t(unsigned int, PAGE_SIZE - offset, len); - - sg_set_page(sg, memmap->pages[i], nbytes, offset); - - buf += nbytes; - len -= nbytes; - } - - nents = dma_map_sg(&memmap->dev->pcidev->dev, memmap->sgt.sgl, memmap->sgt.orig_nents, DMA_BIDIRECTIONAL); - if (!nents) { - dev_err(&memmap->dev->dev, "map sgl failed, sgt 0x%p.\n", &memmap->sgt); - return -EIO; - } - memmap->sgt.nents = nents; - - for (i = 0, sg = memmap->sgt.sgl; i < nents; i++, sg = sg_next(sg)) { - cda_sg_list[i].size = sg_dma_len(sg); - cda_sg_list[i].paddr = sg_dma_address(sg); - } - - memmap->mapped_blk_cnt = nents; - return 0; -} - -int cda_map_mem(struct cda_dev *dev, void *owner, void __user *ureq) -{ - int ret = -ENOMEM; - int idx; - int npages; - struct cda_mmap *memmap; - struct cda_map_mem req; - unsigned long offset; - void *req_vaddr; - - if (copy_from_user(&req, ureq, sizeof(req))) - return -EFAULT; - req_vaddr = (void __user *)req.vaddr; - offset = offset_in_page(req_vaddr); - npages = DIV_ROUND_UP(offset + req.size, PAGE_SIZE); - memmap = kzalloc(sizeof(*memmap) + npages * (sizeof(struct cda_drv_sg_item) + sizeof(struct page *)), - in_atomic() ? GFP_ATOMIC : GFP_KERNEL); - if (!memmap) { - dev_err(&dev->dev, "Can't alloc memmap\n"); - goto out; - } - memmap->owner = owner; - memmap->sg_list = (struct cda_drv_sg_item *)((void *)memmap + sizeof(*memmap)); - memmap->pages = (struct page **)((void *)memmap + sizeof(*memmap) + npages * (sizeof(struct cda_drv_sg_item))); - - if (sg_alloc_table(&memmap->sgt, npages, - in_atomic() ? GFP_ATOMIC :GFP_KERNEL)) { - dev_err(&dev->dev, "Can't alloc sg table\n"); - goto out; - } - INIT_LIST_HEAD(&memmap->list); - memmap->dev = dev; - kobject_init(&memmap->kobj, &memmap_type); - - memmap->vaddr = req_vaddr; - memmap->size = req.size; - memmap->blk_cnt = npages; - idr_preload(in_atomic() ? GFP_ATOMIC : GFP_KERNEL); - spin_lock(&dev->mblk_sl); - ret = idr_alloc(&dev->mblk_idr, memmap, - 1L, 0, in_atomic() ? GFP_ATOMIC : GFP_KERNEL); - spin_unlock(&dev->mblk_sl); - idr_preload_end(); - if (ret < 0) - goto err_idr; - memmap->index = req.index = idx = ret; - - ret = pin_user_pages_fast((ulong)req_vaddr, npages, - FOLL_WRITE, memmap->pages); - if ( ret < 0 ) { - dev_err(&dev->pcidev->dev, - "Pin user pages failed for addr=0x%p [ret=%d]\n", - req_vaddr, ret); - goto err_pin; - } - if (ret != npages) { - dev_err(&dev->pcidev->dev, - "Unable to pin all user pages for addr=0x%p\n", req_vaddr); - ret = -EFAULT; - goto err_pin; - } - - ret = cda_perform_mapping(memmap); - if ( ret ) { - dev_err(&dev->dev, "Can't map user memory for DMA (size %u)", req.size); - goto err_dma_alloc; - } - - ret = cda_publish_memmap(memmap); - if (ret) { - dev_err(&dev->dev, "Can't publish memmap to sysfs: %d", ret); - goto err_publish; - } - - if(copy_to_user(ureq, &req, sizeof(req))) { - ret = -EFAULT; - goto err_copy_to_user; - } - - spin_lock(&dev->mblk_sl); - list_add(&memmap->list, &dev->mem_maps); - spin_unlock(&dev->mblk_sl); - - dev_dbg(&dev->dev, "map vaddr %p, pages %d\n", memmap->vaddr, npages); - return 0; - -err_copy_to_user: - cda_hide_memmap(memmap); -err_publish: - cda_release_map(memmap); -err_dma_alloc: - unpin_user_pages_dirty_lock(memmap->pages, memmap->blk_cnt, 1); -err_pin: - spin_lock(&dev->mblk_sl); - idr_remove(&dev->mblk_idr, idx); - spin_unlock(&dev->mblk_sl); -err_idr: - kobject_put(&memmap->kobj); -out: - if( memmap ) { - if( memmap->pages ) { - kfree(memmap->pages); - } - kfree(memmap); - } - return ret; -} - -static void cda_free_map(struct cda_mmap *memmap) -{ - dev_dbg(&memmap->dev->dev, "unmap vaddr %p, pages %d\n", memmap->vaddr, memmap->blk_cnt); - cda_hide_memmap(memmap); - cda_release_map(memmap); - kobject_put(&memmap->kobj); -} - -int cda_unmap_mem_by_idx(struct cda_dev *dev, void *owner, void __user *ureq) -{ - int memidx; - struct cda_mmap *memmap; - if (copy_from_user(&memidx, (void __user *)ureq, sizeof(memidx))) - return -EFAULT; - - spin_lock(&dev->mblk_sl); - memmap = idr_find(&dev->mblk_idr, memidx); - if (memmap && memmap->index == memidx) { - if( memmap->owner != owner ) - dev_warn(&dev->dev, "Unmap buffer by another user\n"); - idr_replace(&dev->mblk_idr, dev->dummy_blk, memidx); - list_del(&memmap->list); - } else if (memmap) - dev_warn(&dev->dev, "Unmap buffer with index %d, required %d\n", memmap->index, memidx); - spin_unlock(&dev->mblk_sl); - - if (!memmap) - return -ENOENT; // Somebody may already release this block in parallel - - if( memmap->index ) { - cda_free_map(memmap); - spin_lock(&dev->mblk_sl); - idr_remove(&dev->mblk_idr, memmap->index); - spin_unlock(&dev->mblk_sl); - } - return 0; -} - -void cda_unmmap_dev_mem(struct cda_dev *dev, void *owner) -{ - struct cda_mmap *memmap, *tmp; - LIST_HEAD(memmaps); - - spin_lock(&dev->mblk_sl); - if( owner == NULL ){ - list_replace_init(&dev->mem_maps, &memmaps); - } else { - list_for_each_entry_safe(memmap, tmp, &dev->mem_maps, list) { - if( memmap->index > 0L && memmap->owner == owner ) { - idr_replace(&dev->mblk_idr, dev->dummy_blk, memmap->index); - list_move(&memmap->list, &memmaps); - } - } - } - spin_unlock(&dev->mblk_sl); - list_for_each_entry_safe(memmap, tmp, &memmaps, list) { - // Unmap blocks owned by specified owner or all if owner is NULL - cda_free_map(memmap); - if( owner != NULL ){ - spin_lock(&dev->mblk_sl); - idr_remove(&dev->mblk_idr, memmap->index); - spin_unlock(&dev->mblk_sl); - } - } -} - -int cda_mems_create(struct cda_dev *cdadev) -{ - int ret = sysfs_create_group(&cdadev->dev.kobj, &cda_attr_grp); - if (ret) - goto err_group; - - cdadev->kobj_mems = kobject_create_and_add("mems", &cdadev->dev.kobj); - if (!cdadev->kobj_mems) - goto err_mems; - return 0; - -err_mems: - sysfs_remove_group(&cdadev->dev.kobj, &cda_attr_grp); -err_group: - dev_err(&cdadev->dev, "Couldn't create sysfs files: %d\n", ret); - return ret; -} - -void cda_mems_release(struct cda_dev *dev) -{ - //cda_release_bars(dev); - kobject_del(dev->kobj_mems); - kobject_put(dev->kobj_mems); - sysfs_remove_group(&dev->dev.kobj, &cda_attr_grp); -} +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2020 Egor Pomozov. +// +// Originally memalloc sequence was designed for simple driver +// in Aquantia Corp by Vadim Solomin +// Later was updated by QA team in Aquantia Corp. +// Later it was additionally modified by Egor Pomozov +// +// CDA linux driver memory request handler +// +// This program is free software; you can redistribute it and/or modify it +// under the terms and conditions of the GNU General Public License, +// version 2, as published by the Free Software Foundation. +// + +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> + +#include "cdadrv.h" +#include "cdaioctl.h" + +#include <linux/version.h> + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +/** + * pin_user_pages_fast() - pin user pages in memory without taking locks + * + * For now, this is a placeholder function, until various call sites are + * converted to use the correct get_user_pages*() or pin_user_pages*() API. So, + * this is identical to get_user_pages_fast(). + * + * This is intended for Case 1 (DIO) in Documentation/vm/pin_user_pages.rst. It + * is NOT intended for Case 2 (RDMA: long-term pins). + */ +static int pin_user_pages_fast(unsigned long start, int nr_pages, + unsigned int gup_flags, struct page **pages) +{ + /* + * This is a placeholder, until the pin functionality is activated. + * Until then, just behave like the corresponding get_user_pages*() + * routine. + */ + return get_user_pages_fast(start, nr_pages, gup_flags, pages); +} + +/** + * unpin_user_page() - release a gup-pinned page + * @page: pointer to page to be released + * + * Pages that were pinned via pin_user_pages*() must be released via either + * unpin_user_page(), or one of the unpin_user_pages*() routines. This is so + * that eventually such pages can be separately tracked and uniquely handled. In + * particular, interactions with RDMA and filesystems need special handling. + * + * unpin_user_page() and put_page() are not interchangeable, despite this early + * implementation that makes them look the same. unpin_user_page() calls must + * be perfectly matched up with pin*() calls. + */ +static inline void unpin_user_page(struct page *page) +{ + put_page(page); +} + +/** + * unpin_user_pages() - release an array of gup-pinned pages. + * @pages: array of pages to be marked dirty and released. + * @npages: number of pages in the @pages array. + * + * For each page in the @pages array, release the page using unpin_user_page(). + * + * Please see the unpin_user_page() documentation for details. + */ +static void unpin_user_pages(struct page **pages, unsigned long npages) +{ + unsigned long index; + + /* + * TODO: this can be optimized for huge pages: if a series of pages is + * physically contiguous and part of the same compound page, then a + * single operation to the head page should suffice. + */ + for (index = 0; index < npages; index++) + unpin_user_page(pages[index]); +} + +/** + * unpin_user_pages_dirty_lock() - release and optionally dirty gup-pinned pages + * @pages: array of pages to be maybe marked dirty, and definitely released. + * @npages: number of pages in the @pages array. + * @make_dirty: whether to mark the pages dirty + * + * "gup-pinned page" refers to a page that has had one of the get_user_pages() + * variants called on that page. + * + * For each page in the @pages array, make that page (or its head page, if a + * compound page) dirty, if @make_dirty is true, and if the page was previously + * listed as clean. In any case, releases all pages using unpin_user_page(), + * possibly via unpin_user_pages(), for the non-dirty case. + * + * Please see the unpin_user_page() documentation for details. + * + * set_page_dirty_lock() is used internally. If instead, set_page_dirty() is + * required, then the caller should a) verify that this is really correct, + * because _lock() is usually required, and b) hand code it: + * set_page_dirty_lock(), unpin_user_page(). + * + */ +static void unpin_user_pages_dirty_lock(struct page **pages, unsigned long npages, + bool make_dirty) +{ + unsigned long index; + + /* + * TODO: this can be optimized for huge pages: if a series of pages is + * physically contiguous and part of the same compound page, then a + * single operation to the head page should suffice. + */ + + if (!make_dirty) { + unpin_user_pages(pages, npages); + return; + } + + for (index = 0; index < npages; index++) { + struct page *page = compound_head(pages[index]); + /* + * Checking PageDirty at this point may race with + * clear_page_dirty_for_io(), but that's OK. Two key + * cases: + * + * 1) This code sees the page as already dirty, so it + * skips the call to set_page_dirty(). That could happen + * because clear_page_dirty_for_io() called + * page_mkclean(), followed by set_page_dirty(). + * However, now the page is going to get written back, + * which meets the original intention of setting it + * dirty, so all is well: clear_page_dirty_for_io() goes + * on to call TestClearPageDirty(), and write the page + * back. + * + * 2) This code sees the page as clean, so it calls + * set_page_dirty(). The page stays dirty, despite being + * written back, so it gets written back again in the + * next writeback cycle. This is harmless. + */ + if (!PageDirty(page)) + set_page_dirty_lock(page); + unpin_user_page(page); + } +} + +#endif + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cda_dev *cdadev = container_of((dev), struct cda_dev, dev); + + return sprintf(buf, "cda%d\n", cdadev->minor); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *cda_attrs[] = { + &dev_attr_name.attr, + NULL, +}; + +static struct attribute_group cda_attr_grp = { + .attrs = cda_attrs, +}; + +static ssize_t mblk_attr_show( + struct kobject *kobj, + struct attribute *attr, + char *buf); + +static void mblk_release(struct kobject *kobj); + +struct cda_mblk { + struct cda_dev *dev; + int index; + + struct kobject kobj; + uint32_t req_size; + void *vaddr; //kernel + uint32_t size; + dma_addr_t paddr; + void *owner; + struct list_head list; + struct bin_attribute mmap_attr; +}; + +struct cda_mmap { + struct cda_dev *dev; + int index; + + struct kobject kobj; + void *owner; + + void *vaddr; //original user + uint32_t size; //original user + uint32_t blk_cnt; + uint32_t mapped_blk_cnt; + uint32_t show_cnt; + struct sg_table sgt; + struct page **pages; + struct cda_drv_sg_item *sg_list; + struct list_head list; + struct bin_attribute mmap_attr; +}; + +struct mblkitem_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct cda_mblk *, char *); + ssize_t (*store)(struct cda_mblk *, char*, size_t); +}; + +#define cda_dev_mblk_attr(_field, _fmt) \ + static ssize_t \ + mblk_##_field##_show(struct cda_mblk *mblk, char *buf) \ + { \ + return sprintf(buf, _fmt, mblk->_field); \ + } \ + static struct mblkitem_sysfs_entry mblk_##_field##_attr = \ + __ATTR(_field, S_IRUGO, mblk_##_field##_show, NULL) + +#pragma GCC diagnostic ignored "-Wformat" +cda_dev_mblk_attr(vaddr, "0x%lx\n"); +cda_dev_mblk_attr(paddr, "0x%lx\n"); +cda_dev_mblk_attr(size, "0x%x\n"); +cda_dev_mblk_attr(req_size, "0x%x\n"); +cda_dev_mblk_attr(owner, "0x%p\n"); +cda_dev_mblk_attr(index, "%d\n"); +#pragma GCC diagnostic warning "-Wformat" + +static struct attribute *mblk_attrs[] = { + &mblk_vaddr_attr.attr, + &mblk_paddr_attr.attr, + &mblk_size_attr.attr, + &mblk_owner_attr.attr, + &mblk_req_size_attr.attr, + &mblk_index_attr.attr, + NULL, +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) +ATTRIBUTE_GROUPS(mblk); +#endif +static const struct sysfs_ops mblk_ops = { + .show = mblk_attr_show, +}; + +static const struct kobj_type mblk_type = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + .default_groups = mblk_groups, +#else + .default_attrs = mblk_attrs, +#endif + .sysfs_ops = &mblk_ops, + .release = mblk_release, +}; + +static ssize_t mblk_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct cda_mblk *mblk = container_of(kobj, struct cda_mblk, kobj); + struct mblkitem_sysfs_entry *entry = + container_of(attr, struct mblkitem_sysfs_entry, attr); + + if (!entry->show) + return -EIO; + + return entry->show(mblk, buf); +} + +static void mblk_release(struct kobject *kobj) +{ + struct cda_mblk *mblk = container_of(kobj, struct cda_mblk, kobj); + + kfree(mblk); +} + +#define to_memmap(obj) container_of(obj, struct cda_mmap, kobj) + +struct memmapitem_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct cda_mmap *, char *); + ssize_t (*store)(struct cda_mmap *, char *, size_t); +}; + +#define cda_dev_memmap_attr(_field, _fmt) \ + static ssize_t \ + memmap_##_field##_show(struct cda_mmap *memmap, char *buf) \ + { \ + return sprintf(buf, _fmt, memmap->_field); \ + } \ + static struct memmapitem_sysfs_entry memmap_##_field##_attr = \ + __ATTR(_field, S_IRUGO, memmap_##_field##_show, NULL) + +#pragma GCC diagnostic ignored "-Wformat" +cda_dev_memmap_attr(owner, "0x%p\n"); +cda_dev_memmap_attr(vaddr, "0x%lx\n"); +cda_dev_memmap_attr(size, "0x%x\n"); +cda_dev_memmap_attr(index, "%d\n"); +cda_dev_memmap_attr(blk_cnt, "%d\n"); + +static ssize_t +memmap_sglist_show(struct cda_mmap *memmap, char *buf) +{ + const int sg_list_item_size = 16 + 8 + 2; //"%016llx %08lx\n" + int res = 0; + int i = memmap->show_cnt; + + memmap->show_cnt = 0; + buf[0] = '\0'; + for ( ; i < memmap->blk_cnt; i++) { + if ((res + sg_list_item_size) >= (PAGE_SIZE - 1)) /* https://lwn.net/Articles/178634/ */{ + memmap->show_cnt = i; + //printk("Split SG list. Next read starts with item: %d\n", i); + break; + } + res += sprintf(&buf[res], "%016llx %08lx\n", memmap->sg_list[i].paddr, memmap->sg_list[i].size); + } + return res; +} + +static struct memmapitem_sysfs_entry memmap_sglist_attr = + __ATTR(sglist, S_IRUGO, memmap_sglist_show, NULL); + +#pragma GCC diagnostic warning "-Wformat" +static struct attribute *memmap_attrs[] = { + &memmap_owner_attr.attr, + &memmap_vaddr_attr.attr, + &memmap_size_attr.attr, + &memmap_index_attr.attr, + &memmap_blk_cnt_attr.attr, + &memmap_sglist_attr.attr, + NULL, +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) +ATTRIBUTE_GROUPS(memmap); +#endif + +static ssize_t memmap_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct cda_mmap *memmap = to_memmap(kobj); + struct memmapitem_sysfs_entry *entry = + container_of(attr, struct memmapitem_sysfs_entry, attr); + + if (!entry->show) + return -EIO; + + return entry->show(memmap, buf); +} + +static void memmap_release(struct kobject *kobj) +{ + struct cda_mmap *memmap = to_memmap(kobj); + + kfree(memmap); +} + +static const struct sysfs_ops memmap_ops = { + .show = memmap_attr_show, +}; + +static const struct kobj_type memmap_type = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + .default_groups = memmap_groups, +#else + .default_attrs = memmap_attrs, +#endif + .sysfs_ops = &memmap_ops, + .release = memmap_release, +}; + +static int mblk_mmap(struct file *file, + struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + struct cda_mblk *mblk = attr->private; + unsigned long requested = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; + unsigned long pages = (unsigned long)mblk->req_size >> PAGE_SHIFT; + + if (vma->vm_pgoff + requested > pages) + return -EINVAL; + + if (dma_mmap_coherent(&mblk->dev->pcidev->dev, + vma, + mblk->vaddr, + mblk->paddr, + mblk->req_size)) { + dev_err(&mblk->dev->pcidev->dev, "DMA remapping failed"); + return -ENXIO; + } + return 0; +} + +static int cda_publish_mblk(struct cda_mblk *mblk) +{ + int ret; + struct bin_attribute *mmap_attr = &mblk->mmap_attr; + + ret = kobject_add(&mblk->kobj, mblk->dev->kobj_mems, + "%04d", mblk->index); + if (ret) + goto err_add; + + mmap_attr->mmap = mblk_mmap; + mmap_attr->attr.name = "mmap"; + mmap_attr->attr.mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + mmap_attr->size = mblk->req_size; + mmap_attr->private = mblk; + ret = sysfs_create_bin_file(&mblk->kobj, mmap_attr); + if (ret) + goto err_map_add; + + return 0; + +err_map_add: + kobject_del(&mblk->kobj); +err_add: + kobject_put(&mblk->kobj); + return ret; +} + + +static void cda_hide_mblk(struct cda_mblk *mblk) +{ + sysfs_remove_bin_file(&mblk->kobj, &mblk->mmap_attr); + kobject_del(&mblk->kobj); +} + +static int cda_publish_memmap(struct cda_mmap *memmap) +{ + int ret; + struct bin_attribute *mmap_attr = &memmap->mmap_attr; + + ret = kobject_add(&memmap->kobj, memmap->dev->kobj_mems, + "%04d", memmap->index); + if (ret) + goto err_add; + + mmap_attr->attr.name = "memmapobj"; + mmap_attr->attr.mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + mmap_attr->size = memmap->size; + mmap_attr->private = memmap; + ret = sysfs_create_bin_file(&memmap->kobj, mmap_attr); + if (ret) + goto err_map_add; + + return 0; + +err_map_add: + kobject_del(&memmap->kobj); +err_add: + kobject_put(&memmap->kobj); + return ret; +} + +static void cda_hide_memmap(struct cda_mmap *memmap) +{ + sysfs_remove_bin_file(&memmap->kobj, &memmap->mmap_attr); + kobject_del(&memmap->kobj); +} + +int cda_alloc_mem(struct cda_dev *dev, void *owner, void __user *ureq) +{ + int ret = -ENOMEM; + int idx; + struct cda_mblk *mblk; + struct cda_alloc_mem req; + + if (copy_from_user(&req, ureq, sizeof(req))) + return -EFAULT; + + mblk = kzalloc(sizeof(*mblk), in_atomic() ? GFP_ATOMIC : GFP_KERNEL); + if (!mblk) + goto out; + INIT_LIST_HEAD(&mblk->list); + mblk->dev = dev; + kobject_init(&mblk->kobj, &mblk_type); + mblk->owner = owner; + mblk->size = req.size; + req.size = ALIGN(req.size, PAGE_SIZE); + + idr_preload(in_atomic() ? GFP_ATOMIC : GFP_KERNEL); + spin_lock(&dev->mblk_sl); + ret = idr_alloc(&dev->mblk_idr, mblk, + 1L, 0, in_atomic() ? GFP_ATOMIC : GFP_KERNEL); + spin_unlock(&dev->mblk_sl); + idr_preload_end(); + if (ret < 0) + goto err_idr; + mblk->index = req.index = idx = ret; + + mblk->vaddr = dma_alloc_coherent( + &dev->pcidev->dev, + req.size, + &mblk->paddr, + in_atomic() ? GFP_ATOMIC | GFP_KERNEL : GFP_KERNEL); + if (!mblk->vaddr) { + dev_err(&dev->dev, "Can't alloc DMA memory (size %u)", req.size); + ret = -1; + goto err_dma_alloc; + } + mblk->req_size = req.size; + + ret = cda_publish_mblk(mblk); + if (ret) { + dev_err(&dev->dev, "Can't publish mblk to sysfs: %d", ret); + goto err_publish; + } + + if (copy_to_user(ureq, &req, sizeof(req))) { + ret = -EFAULT; + goto err_copy_to_user; + } + + spin_lock(&dev->mblk_sl); + list_add(&mblk->list, &dev->mem_blocks); + spin_unlock(&dev->mblk_sl); + + return 0; + +err_copy_to_user: + cda_hide_mblk(mblk); +err_publish: + dma_free_coherent(&dev->pcidev->dev, mblk->req_size, + mblk->vaddr, mblk->paddr); +err_dma_alloc: + spin_lock(&dev->mblk_sl); + idr_remove(&dev->mblk_idr, idx); + spin_unlock(&dev->mblk_sl); +err_idr: + kobject_put(&mblk->kobj); +out: + return ret; +} + +static void cda_free_mem(struct cda_mblk *mblk) +{ + cda_hide_mblk(mblk); + dma_free_coherent(&mblk->dev->pcidev->dev, mblk->req_size, + mblk->vaddr, mblk->paddr); + kobject_put(&mblk->kobj); +} + +int cda_free_mem_by_idx(struct cda_dev *dev, void *owner, void __user *ureq) +{ + int memidx; + struct cda_mblk *mblk; + + if (copy_from_user(&memidx, (void __user *)ureq, sizeof(memidx))) + return -EFAULT; + + spin_lock(&dev->mblk_sl); + mblk = idr_find(&dev->mblk_idr, memidx); + if (mblk && mblk->index == memidx) { + if (mblk->owner != owner) { + dev_warn(&dev->dev, "Free mblk from another owner\n"); + idr_replace(&dev->mblk_idr, dev->dummy_blk, memidx); + } + list_del(&mblk->list); + } else if (mblk) { + dev_warn(&dev->dev, "Free mblk with index %d, required %d\n", mblk->index, memidx); + } + spin_unlock(&dev->mblk_sl); + if (!mblk) + return -ENOENT; + if (mblk->index) { + cda_free_mem(mblk); + spin_lock(&dev->mblk_sl); + idr_remove(&dev->mblk_idr, mblk->index); + spin_unlock(&dev->mblk_sl); + } + return 0; +} + +void cda_free_dev_mem(struct cda_dev *dev, void *owner) +{ + struct cda_mblk *mblk, *tmp; + LIST_HEAD(mblks); + + spin_lock(&dev->mblk_sl); + if (owner == NULL) { + idr_destroy(&dev->mblk_idr); + list_replace_init(&dev->mem_blocks, &mblks); + } else { + list_for_each_entry_safe(mblk, tmp, &dev->mem_blocks, list) { + if (mblk->index > 0L && mblk->owner == owner) { + list_move(&mblk->list, &mblks); + idr_replace(&dev->mblk_idr, dev->dummy_blk, mblk->index); + } + } + } + spin_unlock(&dev->mblk_sl); + list_for_each_entry_safe(mblk, tmp, &mblks, list) { + // Unmap blocks owned by specified owner or all if owner is NULL + cda_free_mem(mblk); + if (owner != NULL) { + spin_lock(&dev->mblk_sl); + idr_remove(&dev->mblk_idr, mblk->index); + spin_unlock(&dev->mblk_sl); + } + } +} + +static void cda_release_map(struct cda_mmap *memmap) +{ + dma_unmap_sg(memmap->dev->pcidev == NULL ? NULL : &memmap->dev->pcidev->dev, memmap->sgt.sgl, memmap->sgt.orig_nents, DMA_BIDIRECTIONAL); + unpin_user_pages_dirty_lock(memmap->pages, memmap->blk_cnt, 1); + memmap->mapped_blk_cnt = 0; +} + +static int cda_perform_mapping( + struct cda_mmap *memmap) +{ + uint i; + int nents; + struct scatterlist *sg; + ulong len = memmap->size; + void __user *buf = memmap->vaddr; + struct cda_drv_sg_item *cda_sg_list = memmap->sg_list; + + sg = memmap->sgt.sgl; + for (i = 0; i < memmap->sgt.orig_nents; i++, sg = sg_next(sg)) { + unsigned int offset = offset_in_page(buf); + unsigned int nbytes = + min_t(unsigned int, PAGE_SIZE - offset, len); + + sg_set_page(sg, memmap->pages[i], nbytes, offset); + + buf += nbytes; + len -= nbytes; + } + + nents = dma_map_sg(&memmap->dev->pcidev->dev, memmap->sgt.sgl, memmap->sgt.orig_nents, DMA_BIDIRECTIONAL); + if (!nents) { + dev_err(&memmap->dev->dev, "map sgl failed, sgt 0x%p.\n", &memmap->sgt); + return -EIO; + } + memmap->sgt.nents = nents; + + for (i = 0, sg = memmap->sgt.sgl; i < nents; i++, sg = sg_next(sg)) { + cda_sg_list[i].size = sg_dma_len(sg); + cda_sg_list[i].paddr = sg_dma_address(sg); + } + + memmap->mapped_blk_cnt = nents; + return 0; +} + +int cda_map_mem(struct cda_dev *dev, void *owner, void __user *ureq) +{ + int ret = -ENOMEM; + int idx; + int npages; + struct cda_mmap *memmap; + struct cda_map_mem req; + unsigned long offset; + void *req_vaddr; + + if (copy_from_user(&req, ureq, sizeof(req))) + return -EFAULT; + req_vaddr = (void __user *)req.vaddr; + offset = offset_in_page(req_vaddr); + npages = DIV_ROUND_UP(offset + req.size, PAGE_SIZE); + memmap = kzalloc(sizeof(*memmap) + npages * (sizeof(struct cda_drv_sg_item) + sizeof(struct page *)), + in_atomic() ? GFP_ATOMIC : GFP_KERNEL); + if (!memmap) + goto out; + memmap->owner = owner; + memmap->sg_list = (struct cda_drv_sg_item *)((void *)memmap + sizeof(*memmap)); + memmap->pages = (struct page **)((void *)memmap + sizeof(*memmap) + npages * (sizeof(struct cda_drv_sg_item))); + + if (sg_alloc_table(&memmap->sgt, npages, + in_atomic() ? GFP_ATOMIC : GFP_KERNEL)) { + dev_err(&dev->dev, "Can't alloc sg table\n"); + goto out; + } + INIT_LIST_HEAD(&memmap->list); + memmap->dev = dev; + kobject_init(&memmap->kobj, &memmap_type); + + memmap->vaddr = req_vaddr; + memmap->size = req.size; + memmap->blk_cnt = npages; + idr_preload(in_atomic() ? GFP_ATOMIC : GFP_KERNEL); + spin_lock(&dev->mblk_sl); + ret = idr_alloc(&dev->mblk_idr, memmap, + 1L, 0, in_atomic() ? GFP_ATOMIC : GFP_KERNEL); + spin_unlock(&dev->mblk_sl); + idr_preload_end(); + if (ret < 0) + goto err_idr; + memmap->index = req.index = idx = ret; + + ret = pin_user_pages_fast((ulong)req_vaddr, npages, + FOLL_WRITE, memmap->pages); + if (ret < 0) { + dev_err(&dev->pcidev->dev, + "Pin user pages failed for addr=0x%p [ret=%d]\n", + req_vaddr, ret); + goto err_pin; + } + if (ret != npages) { + dev_err(&dev->pcidev->dev, + "Unable to pin all user pages for addr=0x%p\n", req_vaddr); + ret = -EFAULT; + goto err_pin; + } + + ret = cda_perform_mapping(memmap); + if (ret) { + dev_err(&dev->dev, "Can't map user memory for DMA (size %u)", req.size); + goto err_dma_alloc; + } + + ret = cda_publish_memmap(memmap); + if (ret) { + dev_err(&dev->dev, "Can't publish memmap to sysfs: %d", ret); + goto err_publish; + } + + if (copy_to_user(ureq, &req, sizeof(req))) { + ret = -EFAULT; + goto err_copy_to_user; + } + + spin_lock(&dev->mblk_sl); + list_add(&memmap->list, &dev->mem_maps); + spin_unlock(&dev->mblk_sl); + + dev_dbg(&dev->dev, "map vaddr %p, pages %d\n", memmap->vaddr, npages); + return 0; + +err_copy_to_user: + cda_hide_memmap(memmap); +err_publish: + cda_release_map(memmap); +err_dma_alloc: + unpin_user_pages_dirty_lock(memmap->pages, memmap->blk_cnt, 1); +err_pin: + spin_lock(&dev->mblk_sl); + idr_remove(&dev->mblk_idr, idx); + spin_unlock(&dev->mblk_sl); +err_idr: + kobject_put(&memmap->kobj); +out: + if (memmap) { + kfree(memmap->pages); + kfree(memmap); + } + return ret; +} + +static void cda_free_map(struct cda_mmap *memmap) +{ + dev_dbg(&memmap->dev->dev, "unmap vaddr %p, pages %d\n", memmap->vaddr, memmap->blk_cnt); + cda_hide_memmap(memmap); + cda_release_map(memmap); + kobject_put(&memmap->kobj); +} + +int cda_unmap_mem_by_idx(struct cda_dev *dev, void *owner, void __user *ureq) +{ + int memidx; + struct cda_mmap *memmap; + + if (copy_from_user(&memidx, (void __user *)ureq, sizeof(memidx))) + return -EFAULT; + + spin_lock(&dev->mblk_sl); + memmap = idr_find(&dev->mblk_idr, memidx); + if (memmap && memmap->index == memidx) { + if (memmap->owner != owner) + dev_warn(&dev->dev, "Unmap buffer by another user\n"); + idr_replace(&dev->mblk_idr, dev->dummy_blk, memidx); + list_del(&memmap->list); + } else if (memmap) + dev_warn(&dev->dev, "Unmap buffer with index %d, required %d\n", memmap->index, memidx); + spin_unlock(&dev->mblk_sl); + + if (!memmap) + return -ENOENT; // Somebody may already release this block in parallel + + if (memmap->index) { + cda_free_map(memmap); + spin_lock(&dev->mblk_sl); + idr_remove(&dev->mblk_idr, memmap->index); + spin_unlock(&dev->mblk_sl); + } + return 0; +} + +void cda_unmmap_dev_mem(struct cda_dev *dev, void *owner) +{ + struct cda_mmap *memmap, *tmp; + LIST_HEAD(memmaps); + + spin_lock(&dev->mblk_sl); + if (owner == NULL) { + list_replace_init(&dev->mem_maps, &memmaps); + } else { + list_for_each_entry_safe(memmap, tmp, &dev->mem_maps, list) { + if (memmap->index > 0L && memmap->owner == owner) { + idr_replace(&dev->mblk_idr, dev->dummy_blk, memmap->index); + list_move(&memmap->list, &memmaps); + } + } + } + spin_unlock(&dev->mblk_sl); + list_for_each_entry_safe(memmap, tmp, &memmaps, list) { + // Unmap blocks owned by specified owner or all if owner is NULL + cda_free_map(memmap); + if (owner != NULL) { + spin_lock(&dev->mblk_sl); + idr_remove(&dev->mblk_idr, memmap->index); + spin_unlock(&dev->mblk_sl); + } + } +} + +int cda_mems_create(struct cda_dev *cdadev) +{ + int ret; + + ret = sysfs_create_group(&cdadev->dev.kobj, &cda_attr_grp); + if (ret) + goto err_group; + + cdadev->kobj_mems = kobject_create_and_add("mems", &cdadev->dev.kobj); + if (!cdadev->kobj_mems) + goto err_mems; + return 0; + +err_mems: + sysfs_remove_group(&cdadev->dev.kobj, &cda_attr_grp); +err_group: + dev_err(&cdadev->dev, "Couldn't create sysfs files: %d\n", ret); + return ret; +} + +void cda_mems_release(struct cda_dev *dev) +{ + //cda_release_bars(dev); + kobject_del(dev->kobj_mems); + kobject_put(dev->kobj_mems); + sysfs_remove_group(&dev->dev.kobj, &cda_attr_grp); +} diff --git a/src/cdares.c b/src/cdares.c index 2a75df9..102dcfc 100644 --- a/src/cdares.c +++ b/src/cdares.c @@ -1,565 +1,578 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright(c) 2020 DeGirum Corp., Egor Pomozov. -// -// CDA linux driver mem blocks/mem maps and interrupt request handler -// -// This program is free software; you can redistribute it and/or modify it -// under the terms and conditions of the GNU General Public License, -// version 2, as published by the Free Software Foundation. -// -#include <linux/kernel.h> -#include <linux/mm.h> -#include <linux/pci.h> -#include <linux/interrupt.h> -#include <linux/sched.h> -#include <linux/delay.h> -#include <linux/uaccess.h> - -#include "cdadrv.h" -#include "cdaioctl.h" - -struct cda_vector { - volatile bool busy; - wait_queue_head_t wait; - atomic_t count; - unsigned irq; -}; - -struct cda_interrupts { - int num; - enum int_type type; - void *owner; - struct cda_vector *vecs; - struct msix_entry *msix_entries; -}; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) -struct cda_bar { - struct kobject kobj; - /* struct resource *res; */ - int index; - phys_addr_t paddr; - phys_addr_t len; - void *vaddr; - struct cda_dev *dev; - struct bin_attribute mmap_attr; -}; -#else -#if defined __has_attribute -# if __has_attribute (__fallthrough__) -# define fallthrough __attribute__((__fallthrough__)) -# endif -#else -# define fallthrough do {} while (0) /* fallthrough */ -#endif //has_attribute -#endif // LINUX_VERSION_CODE - -static int cda_alloc_msix(struct cda_dev *cdadev, uint32_t rvecs, struct cda_interrupts *ints) -{ - int i, ret; - struct msix_entry *entries; - entries = kcalloc(rvecs, sizeof(struct msix_entry), GFP_KERNEL); - if( !entries ) { - return -ENOMEM; - } - - for (i = 0; i < rvecs; i++) { - entries[i].entry = i; - entries[i].vector = 0; - } - - ret = pci_enable_msix_exact(cdadev->pcidev, entries, rvecs); - if( !ret ) { - ints->num = rvecs; - ints->msix_entries = entries; - ints->type = MSIX; - } else { - kfree(entries); - } - return ret; -} - -static irqreturn_t cda_isr(int irq, void *priv) -{ - struct cda_vector *vec = priv; - atomic_inc_return(&vec->count); - wake_up(&vec->wait); - return IRQ_HANDLED; -} - -int cda_init_interrupts(struct cda_dev *cdadev, void *owner, void __user *ureq) -{ - int ret = 0; - int nvecs, i; - struct cda_vector *vec; - struct cda_int_lock req; - struct cda_interrupts *ints; - - if( cdadev->ints ) { - dev_dbg(&cdadev->pcidev->dev, "Interrupts are already attached"); - return -EINVAL; // Already attached - } - if( copy_from_user(&req, (void __user *)ureq, sizeof(req)) ) - return -EFAULT; - - ints = kcalloc(1, sizeof(struct cda_interrupts), GFP_KERNEL); - if( !ints ) - return -ENOMEM; - - ints->owner = owner; - switch( req.inttype ) { - case MSIX: - ret = cda_alloc_msix(cdadev, req.vectors, ints); - if( !ret ) { - nvecs = req.vectors; - break; - } - if( ret == -ENOMEM ) { - kfree(ints); - return ret; - } - dev_warn(&cdadev->pcidev->dev, "No MSI-X vectors, try MSI. Error %x\n", ret); - fallthrough; - case MSI: -#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) - nvecs = pci_alloc_irq_vectors(cdadev->pcidev, 1, req.vectors, PCI_IRQ_MSI); -#else - nvecs = pci_alloc_irq_vectors_affinity(cdadev->pcidev, 1, req.vectors, PCI_IRQ_MSI, NULL); -#endif - if( nvecs > 0 ) { - ints->num = nvecs; - ints->type = MSI; - break; - } - dev_warn(&cdadev->pcidev->dev, "No MSI vectors, try legacy. Error %x\n", nvecs); - fallthrough; - case LEGACY_INTERRUPT: - ints->num = 1; - ints->type = LEGACY_INTERRUPT; - break; - } - - ints->vecs = kcalloc(ints->num, sizeof(struct cda_vector), GFP_KERNEL); - if( !ints->vecs ) { - ret = -ENOMEM; - goto err_alloc_vecs; - } - - for( i = 0; i < ints->num; i++ ) { - char name[10]; - vec = &ints->vecs[i]; - vec->irq = ints->type == MSIX ? - cdadev->ints->msix_entries[i].vector : - cdadev->pcidev->irq + i; - snprintf(name, sizeof(name), "cda%02d-%x", cdadev->minor, i); - init_waitqueue_head(&vec->wait); - atomic_set(&vec->count, 0); - ret = request_irq(vec->irq, cda_isr, ints->type == LEGACY_INTERRUPT ? IRQF_SHARED : 0, name, vec); - if( ret ) { - dev_err(&cdadev->pcidev->dev, "request_irq failed for vector %d: %d", i, ret); - break; - } - } - - // Return interrupt type and vector count to user - if( !ret ) { - req.inttype = ints->type; - req.vectors = ints->num; - if( copy_to_user(ureq, &req, sizeof(req)) ) - ret = -EFAULT; - } - - if( !ret ) { - cdadev->ints = ints; - return ret; - } - // Fail. Release - for( i -= 1; i >= 0; i-- ) { - struct cda_vector *vec = &ints->vecs[i]; - free_irq(vec->irq, vec); - } - kfree(ints->vecs); - -err_alloc_vecs: - pci_free_irq_vectors(cdadev->pcidev); - kfree(ints->msix_entries); - kfree(ints); - - return ret; -} - -int cda_free_irqs(struct cda_dev *cdadev, void *owner) -{ - int i; - struct cda_interrupts *ints; - if( cdadev->ints == NULL ) - return -EINVAL; - if( cdadev->ints->owner != owner ) { - dev_dbg(&cdadev->pcidev->dev, "Interrupts are not owned by %p", owner); - return -EINVAL; - } - mutex_lock(&cdadev->ilock); - ints = cdadev->ints; - cdadev->ints = NULL; - mutex_unlock(&cdadev->ilock); - if( ints && ints->num > 0 ) { - for( i = 0; i < ints->num; i++ ) { - struct cda_vector *vec = &ints->vecs[i]; - while( vec->busy ) { - wake_up(&vec->wait); - udelay(1); - } - free_irq(vec->irq, vec); - } - pci_free_irq_vectors(cdadev->pcidev); - kfree(ints->vecs); - kfree(ints->msix_entries); - kfree(ints); - } - return 0; -} - -int cda_req_int(struct cda_dev *cdadev, void *owner, void __user *ureq) -{ - struct cda_interrupts *ints; - struct cda_req_int req; - struct cda_vector *vec; - unsigned long timeout; - unsigned count; - - if (copy_from_user(&req, ureq, sizeof(req))) - return -EFAULT; - - if( cdadev->ints == NULL ) - return -EINVAL; - - if( cdadev->ints->owner != owner ) { - dev_err(&cdadev->pcidev->dev, "Interrupts are not owned by %p", owner); - return -EINVAL; - } - - mutex_lock(&cdadev->ilock); - ints = cdadev->ints; - if( !ints || (req.vector > ints->num) ) { - mutex_unlock(&cdadev->ilock); - return -EINVAL; - } - - vec = &ints->vecs[req.vector]; - if (req.reset) - atomic_set(&vec->count, 0); - - timeout = nsecs_to_jiffies(req.timeout); - count = atomic_xchg(&vec->count, 0); - if( !count ) - { - vec->busy = true; - mutex_unlock(&cdadev->ilock); - timeout = wait_event_interruptible_timeout(vec->wait, - (count = atomic_xchg(&vec->count, 0)), - timeout); - mutex_lock(&cdadev->ilock); - vec->busy = false; - } - mutex_unlock(&cdadev->ilock); - dev_dbg(&cdadev->pcidev->dev, "Interrupt vector %d timeout: %ld count %u reset %d\n", req.vector, timeout, count, req.reset); - return timeout > 0 ? 0 : timeout == 0 ? -ETIME : timeout; -} - -int cda_cancel_req(struct cda_dev *cdadev, void *owner) -{ - int i; - struct cda_interrupts *ints; - if( cdadev->ints == NULL ) - return -EINVAL; - - if( cdadev->ints->owner != owner ) { - dev_dbg(&cdadev->pcidev->dev, "Interrupts are not owned by %p", owner); - return -EINVAL; - } - - mutex_lock(&cdadev->ilock); - ints = cdadev->ints; - for (i = 0; ints && i < ints->num; i++) { - if( ints->vecs[i].busy ) - wake_up(&ints->vecs[i].wait); - } - mutex_unlock(&cdadev->ilock); - return 0; -} - -int cda_sem_aq(struct cda_dev *cdadev, void *owner, void __user *ureq) -{ - int res = 0; - struct cda_sem_aq req; - u64 cur_time; - if (copy_from_user(&req, ureq, sizeof(req))) - return -EFAULT; - - mutex_lock(&cdadev->ilock); - cur_time = ktime_get_ns(); - if( cdadev->semaphores[req.sem_id] < cur_time ) { - cdadev->semaphores[req.sem_id] = cur_time + req.time_ns > cur_time ? cur_time + req.time_ns : 0xFFFFFFFFFFFFFFFFULL; - cdadev->sem_owner[req.sem_id] = owner; - } else { - res = 1; - } - mutex_unlock(&cdadev->ilock); - return res; -} - -int cda_sem_rel(struct cda_dev *cdadev, void *owner, void __user *ureq) -{ - int res = 0; - int req_sem; - if (copy_from_user(&req_sem, ureq, sizeof(req_sem))) - return -EFAULT; - if( cdadev->sem_owner[req_sem] != owner ) { - dev_warn(&cdadev->pcidev->dev, "Semaphore %d is not owned by %p", req_sem, owner); - } else { - mutex_lock(&cdadev->ilock); - cdadev->semaphores[req_sem] = 0ULL; - cdadev->sem_owner[req_sem] = NULL; - mutex_unlock(&cdadev->ilock); - } - return res; -} - -void cda_sem_rel_by_owner(struct cda_dev *dev, void *owner) -{ - uint32_t i; - mutex_lock(&dev->ilock); - for( i = 0; i < CDA_MAX_DRV_SEMAPHORES; i++ ) { - if( dev->sem_owner[i] == owner ) { - dev->semaphores[i] = 0ULL; - dev->sem_owner[i] = NULL; - } - } - mutex_unlock(&dev->ilock); -} - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) -#define to_bar(obj) container_of((obj), struct cda_bar, kobj) -struct bar_sysfs_entry { - struct attribute attr; - ssize_t (*show)(struct cda_bar *, char *); - ssize_t (*store)(struct cda_bar *, char*, size_t); -}; - -#define cdadev_bar_attr(_field, _fmt) \ - static ssize_t \ - bar_##_field##_show(struct cda_bar *bar, char *buf) \ - { \ - return sprintf(buf, _fmt, bar->_field); \ - } \ - static struct bar_sysfs_entry bar_##_field##_attr = \ - __ATTR(_field, S_IRUGO, bar_##_field##_show, NULL); - -#pragma GCC diagnostic ignored "-Wformat" -cdadev_bar_attr(paddr, "0x%lx\n"); -cdadev_bar_attr(len, "0x%lx\n"); -cdadev_bar_attr(index, "%d\n"); -#pragma GCC diagnostic warning "-Wformat" - -static ssize_t bar_attr_show(struct kobject *kobj, struct attribute *attr, - char *buf) -{ - struct cda_bar *bar = to_bar(kobj); - struct bar_sysfs_entry *entry = - container_of(attr, struct bar_sysfs_entry, attr); - - if (!entry->show) - return -EIO; - - return entry->show(bar, buf); -} - -static const struct sysfs_ops bar_ops = { - .show = bar_attr_show, -}; - -static void bar_release(struct kobject *kobj) -{ - struct cda_bar *bar = to_bar(kobj); - kfree(bar); -} - -static struct attribute *bar_attrs[] = { - &bar_paddr_attr.attr, - &bar_len_attr.attr, - &bar_index_attr.attr, - NULL, -}; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0) -ATTRIBUTE_GROUPS(bar); -#endif - -static struct kobj_type bar_type = { - .sysfs_ops = &bar_ops, - .release = bar_release, -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0) - .default_groups = bar_groups, -#else - .default_attrs = bar_attrs, -#endif -}; - -// Secure enable support -static const struct vm_operations_struct pci_phys_vm_ops = { -#ifdef CONFIG_HAVE_IOREMAP_PROT - .access = generic_access_phys, -#endif -}; - -static int bar_mmap( struct file *file, - struct kobject *kobj, - struct bin_attribute *attr, - struct vm_area_struct *vma) -{ - struct cda_bar *bar = attr->private; - unsigned long requested = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; - unsigned long pages = (unsigned long)bar->len >> PAGE_SHIFT; - unsigned long size; - - if (vma->vm_pgoff + requested > pages) - return -EINVAL; - - size = ((pci_resource_len(bar->dev->pcidev, bar->index) - 1) >> PAGE_SHIFT) + 1; - if (vma->vm_pgoff + vma_pages(vma) > size) - return -EINVAL; - - vma->vm_page_prot = pgprot_device(vma->vm_page_prot); - vma->vm_pgoff += (pci_resource_start(bar->dev->pcidev, bar->index) >> PAGE_SHIFT); - vma->vm_ops = &pci_phys_vm_ops; - - return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, - vma->vm_end - vma->vm_start, - vma->vm_page_prot); -} - -int cda_open_bars(struct cda_dev *cdadev) -{ - int i; - int ret; - struct cda_bar *bar; - struct resource *res_child; - int bars = pci_select_bars(cdadev->pcidev, IORESOURCE_MEM); - - ret = -EINVAL; - cdadev->kobj_bars = kobject_create_and_add("bars", &cdadev->dev.kobj); - if (!cdadev->kobj_bars) - goto err; - - for (i = 0; bars && i < PCI_ROM_RESOURCE; bars >>= 1, i++){ - struct bin_attribute *mmap_attr; - if (!(bars & 1)) - continue; - - if( !(pci_resource_flags(cdadev->pcidev, i) & IORESOURCE_MEM) ) - continue; - - ret = -ENOMEM; - bar = kzalloc(sizeof(*bar), GFP_KERNEL); - if (!bar) - goto err; - bar->index = i; - bar->paddr = pci_resource_start(cdadev->pcidev, i); - bar->len = pci_resource_len(cdadev->pcidev, i); - bar->vaddr = NULL; - bar->dev = cdadev; - cdadev->sysfs_bar[i] = bar; - kobject_init(&bar->kobj, &bar_type); - - if( (bar->vaddr = pci_iomap(cdadev->pcidev, i, bar->len)) == NULL ) - goto err; - ret = kobject_add(&bar->kobj, cdadev->kobj_bars, "mmio_bar%d", i); - if (ret) - goto err; - - mmap_attr = &bar->mmap_attr; - mmap_attr->mmap = bar_mmap; - mmap_attr->attr.name = "mmap"; - mmap_attr->attr.mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - mmap_attr->size = bar->len; - mmap_attr->private = bar; - - ret = sysfs_create_bin_file(&bar->kobj, mmap_attr); - if (ret) { - dev_err(&cdadev->dev, "Can't create kobject file for mmap"); - goto err; - } - - //Drop busy bit - res_child = cdadev->pcidev->resource[i].child; - - dev_info(&cdadev->dev, "Store resource %d flag: 0x%lx\n", i, res_child->flags); - cdadev->stored_flags[i] = res_child->flags; - if (IORESOURCE_BUSY & res_child->flags) { - res_child->flags &= ~IORESOURCE_BUSY; - } - } - return 0; - -err: - cda_release_bars(cdadev); - return ret; -} - -void cda_release_bars(struct cda_dev *cdadev) -{ - int i; - int bars = pci_select_bars(cdadev->pcidev, IORESOURCE_MEM); - for( i = 0; i < PCI_ROM_RESOURCE; i++ ) { - struct cda_bar *bar = cdadev->sysfs_bar[i]; - if (!bar) - continue; - - cdadev->sysfs_bar[i] = NULL; - sysfs_remove_bin_file(&bar->kobj, &bar->mmap_attr); - kobject_del(&bar->kobj); - kobject_put(&bar->kobj); - - if( bars & (1 << i) ) { - cdadev->pcidev->resource[i].child->flags = cdadev->stored_flags[i]; - dev_info(&cdadev->dev, "Restore resource %d flag: %lx\n", i, cdadev->stored_flags[i]); - } - } - - kobject_del(cdadev->kobj_bars); - kobject_put(cdadev->kobj_bars); -} -#else // LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) -int cda_open_bars(struct cda_dev *cdadev) -{ - int i; - struct resource *res_child; - int bars = pci_select_bars(cdadev->pcidev, IORESOURCE_MEM); - - for( i = 0; i < PCI_ROM_RESOURCE; i++ ) { - // Drop busy bit - if( bars & (1 << i) ) { - res_child = cdadev->pcidev->resource[i].child; - cdadev->stored_flags[i] = res_child->flags; - dev_info(&cdadev->dev, "Store resource %d flag: 0x%lx\n", i, res_child->flags); - if( IORESOURCE_BUSY & res_child->flags ) { - res_child->flags &= ~IORESOURCE_BUSY; - dev_dbg(&cdadev->dev, "Drop busy bit for resource %d", i); - } - } - } - return 0; -} - -void cda_release_bars(struct cda_dev *cdadev) -{ - int i; - int bars = pci_select_bars(cdadev->pcidev, IORESOURCE_MEM); - for( i = 0; i < PCI_ROM_RESOURCE; i++ ) { - if( bars & (1 << i) ) { - cdadev->pcidev->resource[i].child->flags = cdadev->stored_flags[i]; - dev_info(&cdadev->dev, "Restore resource %d flag: %lx\n", i, cdadev->stored_flags[i]); - } - } -} -#endif +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2020 DeGirum Corp., Egor Pomozov. +// +// CDA linux driver mem blocks/mem maps and interrupt request handler +// +// This program is free software; you can redistribute it and/or modify it +// under the terms and conditions of the GNU General Public License, +// version 2, as published by the Free Software Foundation. +// +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/uaccess.h> + +#include "cdadrv.h" +#include "cdaioctl.h" + +struct cda_vector { + volatile bool busy; + wait_queue_head_t wait; + atomic_t count; + unsigned int irq; +}; + +struct cda_interrupts { + int num; + enum int_type type; + void *owner; + struct cda_vector *vecs; + struct msix_entry *msix_entries; +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +struct cda_bar { + struct kobject kobj; + /* struct resource *res; */ + int index; + phys_addr_t paddr; + phys_addr_t len; + void *vaddr; + struct cda_dev *dev; + struct bin_attribute mmap_attr; +}; +#else +#if defined __has_attribute +# if __has_attribute(__fallthrough__) +# define fallthrough __attribute__((__fallthrough__)) +# endif +#else +# define fallthrough do {} while (0) /* fallthrough */ +#endif //has_attribute +#endif // LINUX_VERSION_CODE + +static int cda_alloc_msix(struct cda_dev *cdadev, uint32_t rvecs, struct cda_interrupts *ints) +{ + int i, ret; + struct msix_entry *entries; + + entries = kcalloc(rvecs, sizeof(struct msix_entry), GFP_KERNEL); + if (!entries) + return -ENOMEM; + + for (i = 0; i < rvecs; i++) { + entries[i].entry = i; + entries[i].vector = 0; + } + + ret = pci_enable_msix_exact(cdadev->pcidev, entries, rvecs); + if (!ret) { + ints->num = rvecs; + ints->msix_entries = entries; + ints->type = MSIX; + } else { + kfree(entries); + } + return ret; +} + +static irqreturn_t cda_isr(int irq, void *priv) +{ + struct cda_vector *vec = priv; + + atomic_inc_return(&vec->count); + wake_up(&vec->wait); + return IRQ_HANDLED; +} + +int cda_init_interrupts(struct cda_dev *cdadev, void *owner, void __user *ureq) +{ + int ret = 0; + int nvecs, i; + struct cda_vector *vec; + struct cda_int_lock req; + struct cda_interrupts *ints; + + if (cdadev->ints) { + dev_dbg(&cdadev->pcidev->dev, "Interrupts are already attached"); + return -EINVAL; // Already attached + } + if (copy_from_user(&req, (void __user *)ureq, sizeof(req))) + return -EFAULT; + + ints = kcalloc(1, sizeof(struct cda_interrupts), GFP_KERNEL); + if (!ints) + return -ENOMEM; + + ints->owner = owner; + switch (req.inttype) { + case MSIX: + ret = cda_alloc_msix(cdadev, req.vectors, ints); + if (!ret) { + nvecs = req.vectors; + break; + } + if (ret == -ENOMEM) { + kfree(ints); + return ret; + } + dev_warn(&cdadev->pcidev->dev, "No MSI-X vectors, try MSI. Error %x\n", ret); + fallthrough; + case MSI: +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) + nvecs = pci_alloc_irq_vectors(cdadev->pcidev, 1, req.vectors, PCI_IRQ_MSI); +#else + nvecs = pci_alloc_irq_vectors_affinity(cdadev->pcidev, 1, req.vectors, PCI_IRQ_MSI, NULL); +#endif + if (nvecs > 0) { + ints->num = nvecs; + ints->type = MSI; + break; + } + dev_warn(&cdadev->pcidev->dev, "No MSI vectors, try legacy. Error %x\n", nvecs); + fallthrough; + case LEGACY_INTERRUPT: + ints->num = 1; + ints->type = LEGACY_INTERRUPT; + break; + } + + ints->vecs = kcalloc(ints->num, sizeof(struct cda_vector), GFP_KERNEL); + if (!ints->vecs) { + ret = -ENOMEM; + goto err_alloc_vecs; + } + + for (i = 0; i < ints->num; i++) { + char name[10]; + + vec = &ints->vecs[i]; + vec->irq = ints->type == MSIX ? + cdadev->ints->msix_entries[i].vector : + cdadev->pcidev->irq + i; + snprintf(name, sizeof(name), "cda%02d-%x", cdadev->minor, i); + init_waitqueue_head(&vec->wait); + atomic_set(&vec->count, 0); + ret = request_irq(vec->irq, cda_isr, ints->type == LEGACY_INTERRUPT ? IRQF_SHARED : 0, name, vec); + if (ret) { + dev_err(&cdadev->pcidev->dev, "request_irq failed for vector %d: %d", i, ret); + break; + } + } + + // Return interrupt type and vector count to user + if (!ret) { + req.inttype = ints->type; + req.vectors = ints->num; + if (copy_to_user(ureq, &req, sizeof(req))) + ret = -EFAULT; + } + + if (!ret) { + cdadev->ints = ints; + return ret; + } + // Fail. Release + for (i -= 1; i >= 0; i--) { + struct cda_vector *vec = &ints->vecs[i]; + + free_irq(vec->irq, vec); + } + kfree(ints->vecs); + +err_alloc_vecs: + pci_free_irq_vectors(cdadev->pcidev); + kfree(ints->msix_entries); + kfree(ints); + + return ret; +} + +int cda_free_irqs(struct cda_dev *cdadev, void *owner) +{ + int i; + struct cda_interrupts *ints; + + if (cdadev->ints == NULL) + return -EINVAL; + if (cdadev->ints->owner != owner) { + dev_dbg(&cdadev->pcidev->dev, "Interrupts are not owned by %p", owner); + return -EINVAL; + } + mutex_lock(&cdadev->ilock); + ints = cdadev->ints; + cdadev->ints = NULL; + mutex_unlock(&cdadev->ilock); + if (ints && ints->num > 0) { + for (i = 0; i < ints->num; i++) { + struct cda_vector *vec = &ints->vecs[i]; + + while (vec->busy) { + wake_up(&vec->wait); + udelay(1); + } + free_irq(vec->irq, vec); + } + pci_free_irq_vectors(cdadev->pcidev); + kfree(ints->vecs); + kfree(ints->msix_entries); + kfree(ints); + } + return 0; +} + +int cda_req_int(struct cda_dev *cdadev, void *owner, void __user *ureq) +{ + struct cda_interrupts *ints; + struct cda_req_int req; + struct cda_vector *vec; + unsigned long timeout; + unsigned int count; + + if (copy_from_user(&req, ureq, sizeof(req))) + return -EFAULT; + + if (cdadev->ints == NULL) + return -EINVAL; + + if (cdadev->ints->owner != owner) { + dev_err(&cdadev->pcidev->dev, "Interrupts are not owned by %p", owner); + return -EINVAL; + } + + mutex_lock(&cdadev->ilock); + ints = cdadev->ints; + if (!ints || (req.vector > ints->num)) { + mutex_unlock(&cdadev->ilock); + return -EINVAL; + } + + vec = &ints->vecs[req.vector]; + if (req.reset) + atomic_set(&vec->count, 0); + + timeout = nsecs_to_jiffies(req.timeout); + count = atomic_xchg(&vec->count, 0); + if (!count) { + vec->busy = true; + mutex_unlock(&cdadev->ilock); + timeout = wait_event_interruptible_timeout(vec->wait, + (count = atomic_xchg(&vec->count, 0)), + timeout); + mutex_lock(&cdadev->ilock); + vec->busy = false; + } + mutex_unlock(&cdadev->ilock); + dev_dbg(&cdadev->pcidev->dev, "Interrupt vector %d timeout: %ld count %u reset %d\n", req.vector, timeout, count, req.reset); + return timeout > 0 ? 0 : timeout == 0 ? -ETIME : timeout; +} + +int cda_cancel_req(struct cda_dev *cdadev, void *owner) +{ + int i; + struct cda_interrupts *ints; + + if (cdadev->ints == NULL) + return -EINVAL; + + if (cdadev->ints->owner != owner) { + dev_dbg(&cdadev->pcidev->dev, "Interrupts are not owned by %p", owner); + return -EINVAL; + } + + mutex_lock(&cdadev->ilock); + ints = cdadev->ints; + for (i = 0; ints && i < ints->num; i++) { + if (ints->vecs[i].busy) + wake_up(&ints->vecs[i].wait); + } + mutex_unlock(&cdadev->ilock); + return 0; +} + +int cda_sem_aq(struct cda_dev *cdadev, void *owner, void __user *ureq) +{ + int res = 0; + struct cda_sem_aq req; + u64 cur_time; + + if (copy_from_user(&req, ureq, sizeof(req))) + return -EFAULT; + + mutex_lock(&cdadev->ilock); + cur_time = ktime_get_ns(); + if (cdadev->semaphores[req.sem_id] < cur_time) { + cdadev->semaphores[req.sem_id] = cur_time + req.time_ns > cur_time ? cur_time + req.time_ns : 0xFFFFFFFFFFFFFFFFULL; + cdadev->sem_owner[req.sem_id] = owner; + } else { + res = 1; + } + mutex_unlock(&cdadev->ilock); + return res; +} + +int cda_sem_rel(struct cda_dev *cdadev, void *owner, void __user *ureq) +{ + int res = 0; + int req_sem; + + if (copy_from_user(&req_sem, ureq, sizeof(req_sem))) + return -EFAULT; + if (cdadev->sem_owner[req_sem] != owner) { + dev_warn(&cdadev->pcidev->dev, "Semaphore %d is not owned by %p", req_sem, owner); + } else { + mutex_lock(&cdadev->ilock); + cdadev->semaphores[req_sem] = 0ULL; + cdadev->sem_owner[req_sem] = NULL; + mutex_unlock(&cdadev->ilock); + } + return res; +} + +void cda_sem_rel_by_owner(struct cda_dev *dev, void *owner) +{ + uint32_t i; + + mutex_lock(&dev->ilock); + for (i = 0; i < CDA_MAX_DRV_SEMAPHORES; i++) { + if (dev->sem_owner[i] == owner) { + dev->semaphores[i] = 0ULL; + dev->sem_owner[i] = NULL; + } + } + mutex_unlock(&dev->ilock); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +#define to_bar(obj) container_of((obj), struct cda_bar, kobj) +struct bar_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct cda_bar *, char *); + ssize_t (*store)(struct cda_bar *, char*, size_t); +}; + +#define cdadev_bar_attr(_field, _fmt) \ + static ssize_t \ + bar_##_field##_show(struct cda_bar *bar, char *buf) \ + { \ + return sprintf(buf, _fmt, bar->_field); \ + } \ + static struct bar_sysfs_entry bar_##_field##_attr = \ + __ATTR(_field, S_IRUGO, bar_##_field##_show, NULL) + +#pragma GCC diagnostic ignored "-Wformat" +cdadev_bar_attr(paddr, "0x%lx\n"); +cdadev_bar_attr(len, "0x%lx\n"); +cdadev_bar_attr(index, "%d\n"); +#pragma GCC diagnostic warning "-Wformat" + +static ssize_t bar_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct cda_bar *bar = to_bar(kobj); + struct bar_sysfs_entry *entry = + container_of(attr, struct bar_sysfs_entry, attr); + + if (!entry->show) + return -EIO; + + return entry->show(bar, buf); +} + +static const struct sysfs_ops bar_ops = { + .show = bar_attr_show, +}; + +static void bar_release(struct kobject *kobj) +{ + struct cda_bar *bar = to_bar(kobj); + + kfree(bar); +} + +static struct attribute *bar_attrs[] = { + &bar_paddr_attr.attr, + &bar_len_attr.attr, + &bar_index_attr.attr, + NULL, +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) +ATTRIBUTE_GROUPS(bar); +#endif + +static const struct kobj_type bar_type = { + .sysfs_ops = &bar_ops, + .release = bar_release, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + .default_groups = bar_groups, +#else + .default_attrs = bar_attrs, +#endif +}; + +// Secure enable support +static const struct vm_operations_struct pci_phys_vm_ops = { +#ifdef CONFIG_HAVE_IOREMAP_PROT + .access = generic_access_phys, +#endif +}; + +static int bar_mmap(struct file *file, + struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + struct cda_bar *bar = attr->private; + unsigned long requested = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; + unsigned long pages = (unsigned long)bar->len >> PAGE_SHIFT; + unsigned long size; + + if (vma->vm_pgoff + requested > pages) + return -EINVAL; + + size = ((pci_resource_len(bar->dev->pcidev, bar->index) - 1) >> PAGE_SHIFT) + 1; + if (vma->vm_pgoff + vma_pages(vma) > size) + return -EINVAL; + + vma->vm_page_prot = pgprot_device(vma->vm_page_prot); + vma->vm_pgoff += (pci_resource_start(bar->dev->pcidev, bar->index) >> PAGE_SHIFT); + vma->vm_ops = &pci_phys_vm_ops; + + return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} + +int cda_open_bars(struct cda_dev *cdadev) +{ + int i; + int ret; + struct cda_bar *bar; + struct resource *res_child; + int bars = pci_select_bars(cdadev->pcidev, IORESOURCE_MEM); + + ret = -EINVAL; + cdadev->kobj_bars = kobject_create_and_add("bars", &cdadev->dev.kobj); + if (!cdadev->kobj_bars) + goto err; + + for (i = 0; bars && i < PCI_ROM_RESOURCE; bars >>= 1, i++) { + struct bin_attribute *mmap_attr; + + if (!(bars & 1)) + continue; + + if (!(pci_resource_flags(cdadev->pcidev, i) & IORESOURCE_MEM)) + continue; + + ret = -ENOMEM; + bar = kzalloc(sizeof(*bar), GFP_KERNEL); + if (!bar) + goto err; + bar->index = i; + bar->paddr = pci_resource_start(cdadev->pcidev, i); + bar->len = pci_resource_len(cdadev->pcidev, i); + bar->vaddr = NULL; + bar->dev = cdadev; + cdadev->sysfs_bar[i] = bar; + kobject_init(&bar->kobj, &bar_type); + + bar->vaddr = pci_iomap(cdadev->pcidev, i, bar->len); + if (!bar->vaddr) + goto err; + ret = kobject_add(&bar->kobj, cdadev->kobj_bars, "mmio_bar%d", i); + if (ret) + goto err; + + mmap_attr = &bar->mmap_attr; + mmap_attr->mmap = bar_mmap; + mmap_attr->attr.name = "mmap"; + mmap_attr->attr.mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + mmap_attr->size = bar->len; + mmap_attr->private = bar; + + ret = sysfs_create_bin_file(&bar->kobj, mmap_attr); + if (ret) { + dev_err(&cdadev->dev, "Can't create kobject file for mmap"); + goto err; + } + + //Drop busy bit + res_child = cdadev->pcidev->resource[i].child; + + dev_info(&cdadev->dev, "Store resource %d flag: 0x%lx\n", i, res_child->flags); + cdadev->stored_flags[i] = res_child->flags; + if (IORESOURCE_BUSY & res_child->flags) + res_child->flags &= ~IORESOURCE_BUSY; + } + return 0; + +err: + cda_release_bars(cdadev); + return ret; +} + +void cda_release_bars(struct cda_dev *cdadev) +{ + int i; + int bars = pci_select_bars(cdadev->pcidev, IORESOURCE_MEM); + + for (i = 0; i < PCI_ROM_RESOURCE; i++) { + struct cda_bar *bar = cdadev->sysfs_bar[i]; + + if (!bar) + continue; + + cdadev->sysfs_bar[i] = NULL; + sysfs_remove_bin_file(&bar->kobj, &bar->mmap_attr); + kobject_del(&bar->kobj); + kobject_put(&bar->kobj); + + if (bars & (1 << i)) { + cdadev->pcidev->resource[i].child->flags = cdadev->stored_flags[i]; + dev_info(&cdadev->dev, "Restore resource %d flag: %lx\n", i, cdadev->stored_flags[i]); + } + } + + kobject_del(cdadev->kobj_bars); + kobject_put(cdadev->kobj_bars); +} +#else // LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) +int cda_open_bars(struct cda_dev *cdadev) +{ + int i; + struct resource *res_child; + int bars = pci_select_bars(cdadev->pcidev, IORESOURCE_MEM); + + for (i = 0; i < PCI_ROM_RESOURCE; i++) { + // Drop busy bit + if (bars & (1 << i)) { + res_child = cdadev->pcidev->resource[i].child; + cdadev->stored_flags[i] = res_child->flags; + dev_info(&cdadev->dev, "Store resource %d flag: 0x%lx\n", i, res_child->flags); + if (IORESOURCE_BUSY & res_child->flags) { + res_child->flags &= ~IORESOURCE_BUSY; + dev_dbg(&cdadev->dev, "Drop busy bit for resource %d", i); + } + } + } + return 0; +} + +void cda_release_bars(struct cda_dev *cdadev) +{ + int i; + int bars = pci_select_bars(cdadev->pcidev, IORESOURCE_MEM); + + for (i = 0; i < PCI_ROM_RESOURCE; i++) { + if (bars & (1 << i)) { + cdadev->pcidev->resource[i].child->flags = cdadev->stored_flags[i]; + dev_info(&cdadev->dev, "Restore resource %d flag: %lx\n", i, cdadev->stored_flags[i]); + } + } +} +#endif
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor