Re: [PATCH] fpga: add DFL driver for CXL Cache IP block

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Mar 08, 2024 at 09:23:27AM -0800, Matthew Gerlach wrote:
> From: Tim Whisonant <tim.whisonant@xxxxxxxxx>
> 
> Add a Device Feature List (DFL) driver for the
> Intel CXL Cache IP block. The driver
> provides a means of accessing the device MMIO and the
> capability to pin buffers and program their physical
> addresses into the HE-Cache registers. User interface
> is exposed via /dev/dfl-cxl-cache.X as described in
> include/uapi/linux/fpga-dfl.h.
> 
> Signed-off-by: Tim Whisonant <tim.whisonant@xxxxxxxxx>
> Co-developed-by: Ananda Ravuri <ananda.ravuri@xxxxxxxxx>
> Signed-off-by: Ananda Ravuri <ananda.ravuri@xxxxxxxxx>
> Signed-off-by: Matthew Gerlach <matthew.gerlach@xxxxxxxxxxxxxxx>
> ---
>  drivers/fpga/Kconfig          |  11 +
>  drivers/fpga/Makefile         |   1 +
>  drivers/fpga/dfl-cxl-cache.c  | 645 ++++++++++++++++++++++++++++++++++
>  include/uapi/linux/fpga-dfl.h |  98 ++++++
>  4 files changed, 755 insertions(+)
>  create mode 100644 drivers/fpga/dfl-cxl-cache.c
> 
> diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> index 2f689ac4ba3a..00931a3deebf 100644
> --- a/drivers/fpga/Kconfig
> +++ b/drivers/fpga/Kconfig
> @@ -257,6 +257,17 @@ config FPGA_M10_BMC_SEC_UPDATE
>  	  (BMC) and provides support for secure updates for the BMC image,
>  	  the FPGA image, the Root Entry Hashes, etc.
>  
> +config FPGA_DFL_CXL_CACHE
> +	tristate "Intel CXL cache driver"

The name implies a large scope. If it is not intended to be
generally used by all Intel devices that support CXL cache,
then a more narrow/specific name might be better.

> +	depends on DRM && FPGA_DFL
> +	help
> +	  This is the driver for CXL cache Accelerated Function Unit
> +	  (AFU) which configures the IP and provides DMA buffer management
> +	  to user space.
> +
> +	  To compile this driver as a module, chose M here: the
> +	  module will be called dfl_cxl_cache.
> +
>  config FPGA_MGR_MICROCHIP_SPI
>  	tristate "Microchip Polarfire SPI FPGA manager"
>  	depends on SPI
> diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> index 352a2612623e..970902810845 100644
> --- a/drivers/fpga/Makefile
> +++ b/drivers/fpga/Makefile
> @@ -55,6 +55,7 @@ obj-$(CONFIG_FPGA_DFL_NIOS_INTEL_PAC_N3000)	+= dfl-n3000-nios.o
>  
>  # Drivers for FPGAs which implement DFL
>  obj-$(CONFIG_FPGA_DFL_PCI)		+= dfl-pci.o
> +obj-$(CONFIG_FPGA_DFL_CXL_CACHE)	+= dfl-cxl-cache.o
>  
>  # KUnit tests
>  obj-$(CONFIG_FPGA_KUNIT_TESTS)		+= tests/
> diff --git a/drivers/fpga/dfl-cxl-cache.c b/drivers/fpga/dfl-cxl-cache.c
> new file mode 100644
> index 000000000000..ee2ae04ac058
> --- /dev/null
> +++ b/drivers/fpga/dfl-cxl-cache.c
> @@ -0,0 +1,645 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * DFL device driver for Host Exerciser Cache private feature.
> + *
> + * Provides a means of accessing the device MMIO and the
> + * capability to pin buffers and program their physical
> + * addresses into the HE-Cache registers. User interface
> + * is exposed via /dev/dfl-cxl-cache.X as described in
> + * include/uapi/linux/fpga-dfl.h.
> + *
> + * Copyright (C) 2023 Intel Corporation, Inc.
> + *
> + * Authors:
> + *   Tim Whisonant <tim.whisonant@xxxxxxxxx>
> + *   Ananda Ravuri <ananda.ravuri@xxxxxxxxx>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/cdev.h>
> +#include <linux/cleanup.h>
> +#include <linux/container_of.h>
> +#include <linux/dfl.h>
> +#include <linux/errno.h>
> +#include <linux/fpga-dfl.h>
> +#include <linux/highmem.h>
> +#include <linux/io.h>
> +#include <linux/mmap_lock.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pgtable.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +
> +#include <drm/drm_cache.h>
> +
> +#define DFL_CXL_CACHE_DRIVER_NAME	"dfl-cxl-cache"
> +#define FME_FEATURE_ID_CXL_CACHE	0x25
> +
> +struct dfl_cxl_cache_buffer_region {
> +	struct rb_node node;
> +	u32 flags;
> +	u64 user_addr;
> +	u64 length;
> +	struct page **pages;
> +	phys_addr_t phys;
> +	u64 offset[DFL_ARRAY_MAX_SIZE];
> +};
> +
> +struct dfl_cxl_cache {
> +	struct cdev cdev;
> +	struct dfl_device *ddev;
> +	int id;
> +	struct device *dev;
> +	atomic_t opened;
> +	void __iomem *mmio_base;
> +	int mmio_size;
> +	struct dfl_cxl_cache_region_info rinfo;
> +	struct rb_root dma_regions;
> +};
> +
> +static DEFINE_MUTEX(dfl_cxl_cache_class_lock);
> +static struct class *dfl_cxl_cache_class;
> +static dev_t dfl_cxl_cache_devt;
> +static int dfl_cxl_cache_devices;
> +
> +static int dfl_cxl_cache_open(struct inode *inode, struct file *filp)
> +{
> +	struct dfl_cxl_cache *cxl_cache = container_of(inode->i_cdev, struct dfl_cxl_cache, cdev);
> +
> +	if (atomic_cmpxchg(&cxl_cache->opened, 0, 1))
> +		return -EBUSY;
> +
> +	filp->private_data = cxl_cache;
> +
> +	return 0;
> +}
> +
> +static long cxl_cache_ioctl_check_extension(struct dfl_cxl_cache *cxl_cache, unsigned long arg)
> +{
> +	/* No extension support for now */
> +	return 0;
> +}
> +
> +static long cxl_cache_ioctl_get_region_info(struct dfl_cxl_cache *cxl_cache, void __user *arg)
> +{
> +	struct dfl_cxl_cache_region_info rinfo;
> +	unsigned long minsz;
> +
> +	minsz = offsetofend(struct dfl_cxl_cache_region_info, offset);
> +	if (copy_from_user(&rinfo, arg, minsz))
> +		return -EFAULT;
> +
> +	if (rinfo.argsz < minsz)
> +		return -EINVAL;
> +
> +	rinfo.flags = cxl_cache->rinfo.flags;
> +	rinfo.size = cxl_cache->rinfo.size;
> +	rinfo.offset = cxl_cache->rinfo.offset;
> +
> +	if (copy_to_user(arg, &rinfo, sizeof(rinfo)))
> +		return -EFAULT;
> +
> +	return 0;
> +}
> +
> +static void cxl_cache_unpin_pages(struct device *dev, struct page ***pages, unsigned long length)
> +{
> +	const long npages = PFN_DOWN(length);
> +
> +	if (!*pages)
> +		return;
> +
> +	unpin_user_pages(*pages, npages);
> +	kfree(*pages);
> +	*pages = NULL;
> +	account_locked_vm(current->mm, npages, false);
> +}
> +
> +static bool cxl_cache_check_continuous_pages(struct page **pages, unsigned long length)
> +{
> +	int i;
> +	const int npages = PFN_DOWN(length);
> +
> +	for (i = 0; i < npages - 1; i++)
> +		if (page_to_pfn(pages[i]) + 1 != page_to_pfn(pages[i + 1]))
> +			return false;
> +
> +	return true;
> +}
> +
> +static int cxl_cache_dma_pin_pages(struct dfl_cxl_cache *cxl_cache,
> +				   struct dfl_cxl_cache_buffer_region *region)
> +{
> +	int ret, pinned;
> +	unsigned int flags = FOLL_LONGTERM;
> +	const int npages = PFN_DOWN(region->length);
> +
> +	ret = account_locked_vm(current->mm, npages, true);
> +	if (ret) {
> +		dev_err(cxl_cache->dev, "account_locked_vm() failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	region->pages = kcalloc(npages, sizeof(struct page *), GFP_KERNEL);
> +	if (!region->pages) {
> +		ret = -ENOMEM;
> +		goto unlock_vm;
> +	}
> +
> +	if (region->flags & DFL_CXL_BUFFER_MAP_WRITABLE)
> +		flags |= FOLL_WRITE;
> +
> +	pinned = pin_user_pages_fast(region->user_addr, npages, flags, region->pages);
> +	if (pinned == npages)
> +		return 0;
> +
> +	ret = -EFAULT;
> +	if (pinned > 0)
> +		unpin_user_pages(region->pages, pinned);
> +
> +	kfree(region->pages);
> +unlock_vm:
> +	account_locked_vm(current->mm, npages, false);
> +	return ret;
> +}
> +
> +static void cxl_cache_dma_region_remove(struct dfl_cxl_cache *cxl_cache,
> +					struct dfl_cxl_cache_buffer_region *region)
> +{
> +	rb_erase(&region->node, &cxl_cache->dma_regions);
> +}
> +
> +static bool dma_region_check_user_addr(struct dfl_cxl_cache_buffer_region *region, u64 user_addr,
> +				       u64 size)
> +{
> +	if (!size && region->user_addr != user_addr)
> +		return false;

So, if size == 0, that is okay  as long as (region->user_addr == user_addr)?

> +
> +	return (region->user_addr <= user_addr) &&
> +		(region->length + region->user_addr >= user_addr + size);

Do you need to worry about overflow when adding (user_addr + size)?
These values come from the user, right?

> +}
> +
> +static struct dfl_cxl_cache_buffer_region*
> +cxl_cache_dma_region_find(struct dfl_cxl_cache *cxl_cache, u64 user_addr, u64 size)
> +{
> +	struct rb_node *node = cxl_cache->dma_regions.rb_node;
> +
> +	while (node) {
> +		struct dfl_cxl_cache_buffer_region *region;
> +
> +		region = container_of(node, struct dfl_cxl_cache_buffer_region, node);
> +
> +		if (dma_region_check_user_addr(region, user_addr, size))
> +			return region;
> +
> +		if (user_addr < region->user_addr)
> +			node = node->rb_left;
> +		else if (user_addr > region->user_addr)
> +			node = node->rb_right;
> +		else
> +			break;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int cxl_cache_dma_region_add(struct dfl_cxl_cache *cxl_cache,
> +				    struct dfl_cxl_cache_buffer_region *region)
> +{
> +	struct rb_node **new, *parent = NULL;
> +
> +	new = &cxl_cache->dma_regions.rb_node;
> +
> +	while (*new) {
> +		struct dfl_cxl_cache_buffer_region *this;
> +
> +		this = container_of(*new, struct dfl_cxl_cache_buffer_region, node);
> +		parent = *new;
> +
> +		if (dma_region_check_user_addr(this, region->user_addr, region->length))
> +			return -EEXIST;
> +
> +		if (region->user_addr < this->user_addr)
> +			new = &((*new)->rb_left);
> +		else if (region->user_addr > this->user_addr)
> +			new = &((*new)->rb_right);
> +		else
> +			return -EEXIST;
> +	}
> +
> +	rb_link_node(&region->node, parent, new);
> +	rb_insert_color(&region->node, &cxl_cache->dma_regions);
> +
> +	return 0;
> +}
> +
> +static void fixup_ptes(struct mm_struct *mm, unsigned long start, unsigned long end)
> +{
> +	unsigned long addr;
> +	pgd_t *pgd;
> +	p4d_t *p4d;
> +	pud_t *pud;
> +	pmd_t *pmd;
> +	pte_t *pte;
> +
> +	for (addr = start; addr < end; addr += PAGE_SIZE) {
> +		pgd = pgd_offset(mm, addr);
> +		if (pgd_bad(*pgd) || pgd_none(*pgd))
> +			continue;
> +
> +		p4d = p4d_offset(pgd, addr);
> +		if (p4d_bad(*p4d) || p4d_none(*p4d))
> +			continue;
> +
> +		pud = pud_offset(p4d, addr);
> +		if (pud_bad(*pud) || pud_none(*pud))
> +			continue;
> +
> +		pmd = pmd_offset(pud, addr);
> +		if (pmd_bad(*pmd) || pmd_none(*pmd))
> +			continue;
> +
> +		pte = pte_offset_kernel(pmd, addr);
> +		if (!pte_none(*pte) && pte_present(*pte))
> +			*pte = pte_wrprotect(*pte);
> +	}
> +}
> +
> +static long cxl_cache_set_region_read_only(struct dfl_cxl_cache *cxl_cache,
> +					   struct dfl_cxl_cache_buffer_region *region)
> +{
> +	struct vm_area_struct *vma;
> +	long ret = 0;
> +
> +	vma = vma_lookup(current->mm, region->user_addr);
> +	if (IS_ERR(vma)) {
> +		ret = PTR_ERR(vma);
> +		dev_err(cxl_cache->dev, "vma_lookup() failed: %ld\n", ret);
> +		return ret;
> +	}
> +
> +	mmap_write_lock(current->mm);
> +
> +	/* Mark the pages as non-cached and write-protected. */
> +	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> +	vm_flags_clear(vma, VM_WRITE);
> +
> +	fixup_ptes(current->mm, vma->vm_start, vma->vm_end);
> +
> +	mmap_write_unlock(current->mm);
> +
> +	/* Flush all remaining cache entries. */
> +	drm_clflush_virt_range(page_address(region->pages[0]), region->length);
> +
> +	return ret;
> +}
> +
> +static long cxl_cache_ioctl_numa_buffer_map(struct dfl_cxl_cache *cxl_cache, void __user *arg)
> +{
> +	int i = 0;
> +	unsigned long minsz = 0;
> +	long ret = 0;
> +	struct dfl_cxl_cache_buffer_map dma_map;
> +	struct dfl_cxl_cache_buffer_region *region;
> +
> +	minsz = offsetofend(struct dfl_cxl_cache_buffer_map, csr_array);
> +	if (copy_from_user(&dma_map, arg, minsz)) {
> +		dev_err(cxl_cache->dev, "fails to copy from user space buffer\n");
> +		return -EFAULT;
> +	}
> +	if (dma_map.argsz < minsz) {
> +		dev_err(cxl_cache->dev, "invalid ioctl buffer size\n");
> +		return -EINVAL;
> +	}
> +
> +	/* Check Inputs, only accept page-aligned user memory region with valid length */
> +	if (!PAGE_ALIGNED(dma_map.user_addr) || !PAGE_ALIGNED(dma_map.length) ||
> +	    !(dma_map.length)) {
> +		dev_err(cxl_cache->dev, "length is not page-aligned or the length is zero\n");
> +		return -EINVAL;
> +	}
> +
> +	/* Check overflow */
> +	if (dma_map.user_addr + dma_map.length < dma_map.user_addr) {
> +		dev_err(cxl_cache->dev, "dma buffer check overflow\n");
> +		return -EINVAL;
> +	}
> +
> +	region = kzalloc(sizeof(*region), GFP_KERNEL);
> +	if (!region)
> +		return -ENOMEM;
> +
> +	region->flags = dma_map.flags;
> +	region->user_addr = dma_map.user_addr;
> +	region->length = dma_map.length;
> +
> +	/* Pin the user memory region */
> +	ret = cxl_cache_dma_pin_pages(cxl_cache, region);
> +	if (ret) {
> +		dev_err(cxl_cache->dev, "failed to pin pages\n");
> +		goto free_region;
> +	}
> +
> +	/* Only accept continuous pages, return error else */
> +	if (!cxl_cache_check_continuous_pages(region->pages, region->length)) {
> +		dev_err(cxl_cache->dev, "pages are not continuous\n");
> +		ret = -EINVAL;
> +		goto out_unpin_pages;
> +	}
> +
> +	if (!(region->flags & DFL_CXL_BUFFER_MAP_WRITABLE)) {
> +		ret = cxl_cache_set_region_read_only(cxl_cache, region);
> +		if (ret)
> +			goto out_unpin_pages;
> +	}
> +
> +	ret = cxl_cache_dma_region_add(cxl_cache, region);
> +	if (ret) {
> +		dev_err(cxl_cache->dev, "failed to add dma region\n");
> +		goto out_unpin_pages;
> +	}
> +
> +	region->phys = page_to_phys(region->pages[0]);
> +
> +	for (i = 0; i < DFL_ARRAY_MAX_SIZE; i++) {
> +		if (dma_map.csr_array[i] && dma_map.csr_array[i] < cxl_cache->rinfo.size)
> +			writeq(region->phys, cxl_cache->mmio_base + dma_map.csr_array[i]);
> +	}
> +
> +	return 0;
> +
> +out_unpin_pages:
> +	cxl_cache_unpin_pages(cxl_cache->dev, &region->pages, region->length);
> +free_region:
> +	kfree(region);
> +	return ret;
> +}
> +
> +static long cxl_cache_ioctl_numa_buffer_unmap(struct dfl_cxl_cache *cxl_cache, void __user *arg)
> +{
> +	unsigned long minsz = 0;
> +	long ret = 0;
> +	int i = 0;
> +	struct dfl_cxl_cache_buffer_unmap dma_unmap;
> +	struct dfl_cxl_cache_buffer_region *region;
> +
> +	minsz = offsetofend(struct dfl_cxl_cache_buffer_unmap, csr_array);
> +	if (copy_from_user(&dma_unmap, arg, minsz)) {
> +		dev_err(cxl_cache->dev, "fails to copy from user space buffer\n");
> +		return -EFAULT;
> +	}
> +	if (dma_unmap.argsz < minsz) {
> +		dev_err(cxl_cache->dev, "invalid ioctl buffer size\n");
> +		return -EINVAL;
> +	}
> +
> +	region = cxl_cache_dma_region_find(cxl_cache, dma_unmap.user_addr, dma_unmap.length);
> +	if (!region) {
> +		dev_err(cxl_cache->dev, "fails to find buffer\n");
> +		return -EINVAL;
> +	}
> +
> +	cxl_cache_dma_region_remove(cxl_cache, region);
> +	cxl_cache_unpin_pages(cxl_cache->dev, &region->pages, region->length);
> +
> +	for (i = 0; i < DFL_ARRAY_MAX_SIZE; i++) {
> +		if (dma_unmap.csr_array[i] && dma_unmap.csr_array[i] < cxl_cache->rinfo.size)
> +			writeq(0, cxl_cache->mmio_base + dma_unmap.csr_array[i]);
> +	}
> +
> +	kfree(region);
> +	return ret;
> +}
> +
> +static long dfl_cxl_cache_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> +{
> +	struct dfl_cxl_cache *cxl_cache = filp->private_data;
> +
> +	switch (cmd) {
> +	case DFL_FPGA_GET_API_VERSION:
> +		return DFL_FPGA_GET_API_VERSION;
> +	case DFL_FPGA_CHECK_EXTENSION:
> +		return cxl_cache_ioctl_check_extension(cxl_cache, arg);
> +	case DFL_CXL_CACHE_GET_REGION_INFO:
> +		return cxl_cache_ioctl_get_region_info(cxl_cache, (void __user *)arg);
> +	case DFL_CXL_CACHE_NUMA_BUFFER_MAP:
> +		return cxl_cache_ioctl_numa_buffer_map(cxl_cache, (void __user *)arg);
> +	case DFL_CXL_CACHE_NUMA_BUFFER_UNMAP:
> +		return cxl_cache_ioctl_numa_buffer_unmap(cxl_cache, (void __user *)arg);
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct vm_operations_struct cxl_cache_vma_ops = {
> +#ifdef CONFIG_HAVE_IOREMAP_PROT
> +	.access = generic_access_phys,
> +#endif
> +};
> +
> +static int dfl_cxl_cache_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> +	struct dfl_cxl_cache *cxl_cache = filp->private_data;
> +	u64 size = vma->vm_end - vma->vm_start;
> +	u64 offset;
> +
> +	if (!(vma->vm_flags & VM_SHARED))
> +		return -EINVAL;
> +
> +	if (!(cxl_cache->rinfo.flags & DFL_CXL_CACHE_REGION_MMAP))
> +		return -EINVAL;
> +
> +	if ((vma->vm_flags & VM_READ) && !(cxl_cache->rinfo.flags & DFL_CXL_CACHE_REGION_READ))
> +		return -EPERM;
> +
> +	if ((vma->vm_flags & VM_WRITE) && !(cxl_cache->rinfo.flags & DFL_CXL_CACHE_REGION_WRITE))
> +		return -EPERM;
> +
> +	offset = PFN_PHYS(vma->vm_pgoff);
> +
> +	/* Support debug access to the mapping */
> +	vma->vm_ops = &cxl_cache_vma_ops;
> +
> +	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> +
> +	return remap_pfn_range(vma, vma->vm_start,
> +			       PFN_DOWN(cxl_cache->ddev->mmio_res.start +
> +			       (offset - cxl_cache->rinfo.offset)),
> +			       size, vma->vm_page_prot);
> +}
> +
> +static void cxl_cache_dma_region_destroy(struct dfl_cxl_cache *cxl_cache)
> +{
> +	struct rb_node *node = rb_first(&cxl_cache->dma_regions);
> +	struct dfl_cxl_cache_buffer_region *region;
> +
> +	while (node) {
> +		region = container_of(node, struct dfl_cxl_cache_buffer_region, node);
> +
> +		rb_erase(node, &cxl_cache->dma_regions);
> +
> +		if (region->pages)
> +			cxl_cache_unpin_pages(cxl_cache->dev, &region->pages, region->length);
> +
> +		node = rb_next(node);
> +		kfree(region);
> +	}
> +}
> +
> +static int dfl_cxl_cache_release(struct inode *inode, struct file *filp)
> +{
> +	struct dfl_cxl_cache *cxl_cache = filp->private_data;
> +
> +	cxl_cache_dma_region_destroy(cxl_cache);
> +	atomic_set(&cxl_cache->opened, 0);
> +	return 0;
> +}
> +
> +static const struct file_operations dfl_cxl_cache_fops = {
> +	.owner = THIS_MODULE,
> +	.open = dfl_cxl_cache_open,
> +	.release = dfl_cxl_cache_release,
> +	.unlocked_ioctl = dfl_cxl_cache_ioctl,
> +	.mmap = dfl_cxl_cache_mmap,
> +};
> +
> +static void cxl_cache_dev_release(struct device *dev)
> +{
> +	struct dfl_cxl_cache *cxl_cache = dev_get_drvdata(dev);
> +
> +	cdev_del(&cxl_cache->cdev);
> +}
> +
> +static void cxl_cache_chardev_uinit(struct dfl_cxl_cache *cxl_cache)
> +{
> +	device_destroy(dfl_cxl_cache_class,
> +		       MKDEV(MAJOR(dfl_cxl_cache_devt), cxl_cache->id));
> +}
> +
> +static int cxl_cache_chardev_init(struct dfl_cxl_cache *cxl_cache,
> +				  struct dfl_device *ddev,
> +				  void __iomem *mmio_base)
> +{
> +	int ret;
> +
> +	dev_set_drvdata(&ddev->dev, cxl_cache);
> +	cxl_cache->ddev = ddev;
> +	cxl_cache->mmio_base = mmio_base;
> +	cxl_cache->id = dfl_cxl_cache_devices++;
> +	cxl_cache->dma_regions = RB_ROOT;
> +
> +	cxl_cache->rinfo.argsz = sizeof(struct dfl_cxl_cache_region_info);
> +	cxl_cache->rinfo.flags = DFL_CXL_CACHE_REGION_READ | DFL_CXL_CACHE_REGION_WRITE |
> +			   DFL_CXL_CACHE_REGION_MMAP;
> +	cxl_cache->rinfo.size = resource_size(&ddev->mmio_res);
> +	cxl_cache->rinfo.offset = 0;
> +
> +	cxl_cache->dev = device_create(dfl_cxl_cache_class, &ddev->dev,
> +				       MKDEV(MAJOR(dfl_cxl_cache_devt), cxl_cache->id),
> +				       cxl_cache, DFL_CXL_CACHE_DRIVER_NAME ".%d",
> +				       cxl_cache->id);
> +
> +	if (IS_ERR(cxl_cache->dev)) {
> +		ret = PTR_ERR(cxl_cache->dev);
> +		dev_err(&ddev->dev, "device_create failed: %d\n", ret);
> +		cxl_cache->dev = NULL;
> +		return ret;
> +	}
> +	cxl_cache->dev->release = cxl_cache_dev_release;
> +
> +	cdev_init(&cxl_cache->cdev, &dfl_cxl_cache_fops);
> +	cxl_cache->cdev.owner = THIS_MODULE;
> +	cxl_cache->cdev.ops = &dfl_cxl_cache_fops;
> +
> +	ret = cdev_add(&cxl_cache->cdev, cxl_cache->dev->devt, 1);
> +	if (ret)
> +		dev_err(cxl_cache->dev, "cdev_add failed: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int dfl_cxl_cache_probe(struct dfl_device *ddev)
> +{
> +	int ret = 0;
> +	void __iomem *mmio_base;
> +	struct dfl_cxl_cache *cxl_cache;
> +
> +	guard(mutex)(&dfl_cxl_cache_class_lock);
> +
> +	if (!dfl_cxl_cache_class) {
> +		dfl_cxl_cache_class = class_create(DFL_CXL_CACHE_DRIVER_NAME);
> +		if (IS_ERR(dfl_cxl_cache_class)) {
> +			ret = PTR_ERR(dfl_cxl_cache_class);
> +			dfl_cxl_cache_class = NULL;
> +			dev_err_probe(&ddev->dev, ret, "class_create failed\n");
> +			return ret;

return dev_err_probe(&ddev->dev, ret, "class_create failed\n");

> +		}
> +	}
> +
> +	if (!MAJOR(dfl_cxl_cache_devt)) {
> +		ret = alloc_chrdev_region(&dfl_cxl_cache_devt, 0,
> +					  MINORMASK,
> +					  DFL_CXL_CACHE_DRIVER_NAME);

Is there a case where alloc_chrdev_region() needs to be undone
(unregister_chrdev_region()) before returning from the probe function
because of an error below?

> +		if (ret) {
> +			dev_err_probe(&ddev->dev, ret, "alloc_chrdev_region failed\n");
> +			dfl_cxl_cache_devt = MKDEV(0, 0);
> +			return ret;

return dev_err_probe(&ddev->dev, ret, "alloc_chrdev_region failed\n");

> +		}
> +	}
> +
> +	mmio_base = devm_ioremap_resource(&ddev->dev, &ddev->mmio_res);
> +	if (IS_ERR(mmio_base))
> +		return PTR_ERR(mmio_base);
> +
> +	cxl_cache = devm_kzalloc(&ddev->dev, sizeof(*cxl_cache), GFP_KERNEL);
> +	if (!cxl_cache)
> +		return -ENOMEM;
> +
> +	ret = cxl_cache_chardev_init(cxl_cache, ddev, mmio_base);
> +	if (ret)
> +		dev_err_probe(&ddev->dev, ret, "cxl_cache_chardev_init failed\n");
> +
> +	return ret;
> +}
> +
> +static void dfl_cxl_cache_remove(struct dfl_device *ddev)
> +{
> +	struct dfl_cxl_cache *cxl_cache = dev_get_drvdata(&ddev->dev);
> +
> +	guard(mutex)(&dfl_cxl_cache_class_lock);
> +	cxl_cache_chardev_uinit(cxl_cache);
> +
> +	if (dfl_cxl_cache_devices-- == 0) {


Does dfl_cxl_cache_devices need to be decremented _before_
it is evaluated?

        if (--dfl_cxl_cache_devices == 0) {

> +		if (dfl_cxl_cache_class) {
> +			class_destroy(dfl_cxl_cache_class);
> +			dfl_cxl_cache_class = NULL;
> +		}
> +
> +		if (MAJOR(dfl_cxl_cache_devt)) {
> +			unregister_chrdev_region(dfl_cxl_cache_devt, MINORMASK);
> +			dfl_cxl_cache_devt = MKDEV(0, 0);
> +		}
> +	}
> +}
> +
> +static const struct dfl_device_id dfl_cxl_cache_ids[] = {
> +	{ FME_ID, FME_FEATURE_ID_CXL_CACHE },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(dfl, dfl_cxl_cache_ids);
> +
> +static struct dfl_driver dfl_cxl_cache_driver = {
> +	.drv	= {
> +		.name	= DFL_CXL_CACHE_DRIVER_NAME,
> +	},
> +	.id_table = dfl_cxl_cache_ids,
> +	.probe   = dfl_cxl_cache_probe,
> +	.remove = dfl_cxl_cache_remove,
> +};
> +module_dfl_driver(dfl_cxl_cache_driver);
> +
> +MODULE_DESCRIPTION("DFL CXL Cache driver");
> +MODULE_AUTHOR("Intel Corporation");
> +MODULE_LICENSE("GPL");
> diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h
> index 1621b077bf21..866f50b99eb6 100644
> --- a/include/uapi/linux/fpga-dfl.h
> +++ b/include/uapi/linux/fpga-dfl.h
> @@ -31,6 +31,7 @@
>  #define DFL_FPGA_BASE 0
>  #define DFL_PORT_BASE 0x40
>  #define DFL_FME_BASE 0x80
> +#define DFL_CXL_CACHE_BASE 0xA0
>  
>  /* Common IOCTLs for both FME and AFU file descriptor */
>  
> @@ -276,4 +277,101 @@ struct dfl_fpga_fme_port_pr {
>  					     DFL_FME_BASE + 4,	\
>  					     struct dfl_fpga_irq_set)
>  
> + /**
> +  * DFL_CXL_CACHE_GET_REGION_INFO - _IOWR(DFL_FPGA_MAGIC, DFL_CXL_CACHE_BASE + 0,
> +  *                                      struct dfl_cxl_cache_region_info)
> +  *
> +  * Retrieve information about a device memory region.
> +  * Caller provides struct dfl_cxl_cache_region_info with flags.
> +  * Driver returns the region info in other fields.
> +  * Return: 0 on success, -errno on failure.
> +  */
> +
> +#define DFL_CXL_CACHE_GET_REGION_INFO _IO(DFL_FPGA_MAGIC, DFL_CXL_CACHE_BASE + 0)
> +
> +  /**
> +   * struct dfl_cxl_cache_region_info - CXL cache region information
> +   * @argsz: structure length
> +   * @flags: access permission
> +   * @size: region size (bytes)
> +   * @offset: region offset from start of device fd
> +   *
> +   * to retrieve  information about a device memory region
> +   */
> +struct dfl_cxl_cache_region_info {
> +	__u32 argsz;
> +	__u32 flags;
> +#define DFL_CXL_CACHE_REGION_READ	_BITUL(0)
> +#define DFL_CXL_CACHE_REGION_WRITE	_BITUL(1)
> +#define DFL_CXL_CACHE_REGION_MMAP	_BITUL(2)
> +	__u64 size;
> +	__u64 offset;
> +};
> +
> +/**
> + * DFL_CXL_CACHE_NUMA_BUFFER_MAP - _IOWR(DFL_FPGA_MAGIC, DFL_CXL_CACHE_BASE + 1,
> + *                                      struct dfl_cxl_cache_buffer_map)
> + *
> + * Map the user memory per user_addr, length and numa node which are
> + * provided by caller. The driver allocates memory on the numa node,
> + * converts the user's virtual addressto a continuous physical address,
> + * and writes the physical address to the cxl cache read/write address table CSR.
> + *
> + * This interface only accepts page-size aligned user memory for mapping.
> + * Return: 0 on success, -errno on failure.
> + */
> +
> +#define DFL_ARRAY_MAX_SIZE   0x10
> +
> +#define DFL_CXL_CACHE_NUMA_BUFFER_MAP    _IO(DFL_FPGA_MAGIC,  DFL_CXL_CACHE_BASE + 1)
> +
> +/**
> + * struct dfl_cxl_cache_buffer_map - maps user address to physical address.
> + * @argsz: structure length
> + * @flags: flags
> + * @user_addr: user mmap virtual address
> + * @length: length of mapping (bytes)
> + * @csr_array: array of region address offset
> + *
> + * maps user allocated virtual address to physical address.
> + */
> +struct dfl_cxl_cache_buffer_map {
> +	__u32 argsz;
> +#define DFL_CXL_BUFFER_MAP_WRITABLE	1
> +	__u32 flags;
> +	__u64 user_addr;
> +	__u64 length;
> +	__u64 csr_array[DFL_ARRAY_MAX_SIZE];
> +};
> +
> +/**
> + * DFL_CXL_CACHE_NUMA_BUFFER_UNMAP - _IOWR(DFL_FPGA_MAGIC, DFL_CXL_CACHE_BASE + 1,
> + *                                      struct dfl_cxl_cache_buffer_unmap)
> + *
> + * Unmaps the user memory per user_addr and length which are provided by caller
> + * The driver deletes the physical pages of the user address and writes a zero
> + * to the read/write address table CSR.
> + * Return: 0 on success, -errno on failure.
> + */
> +
> +#define DFL_CXL_CACHE_NUMA_BUFFER_UNMAP  _IO(DFL_FPGA_MAGIC,  DFL_CXL_CACHE_BASE + 2)
> +
> +/**
> + * struct dfl_cxl_cache_buffer_unmap - unmaps user allocated memory.
> + * @argsz: structure length
> + * @flags: flags
> + * @user_addr: user mmap virtual address
> + * @length: length of mapping (bytes)
> + * @csr_array: array of region address offset
> + *
> + * unmaps user allocated memory.
> + */
> +struct dfl_cxl_cache_buffer_unmap {
> +	__u32 argsz;
> +	__u32 flags;
> +	__u64 user_addr;
> +	__u64 length;
> +	__u64 csr_array[DFL_ARRAY_MAX_SIZE];
> +};
> +
>  #endif /* _UAPI_LINUX_FPGA_DFL_H */
> -- 
> 2.34.1
> 




[Index of Archives]     [LM Sensors]     [Linux Sound]     [ALSA Users]     [ALSA Devel]     [Linux Audio Users]     [Linux Media]     [Kernel]     [Gimp]     [Yosemite News]     [Linux Media]

  Powered by Linux