Commit f8eeb398 authored by zhangshuowen96's avatar zhangshuowen96
Browse files

drivers: misc: sdma-dae: support channel management

kunpeng inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I9W355


CVE: NA

----------------------------------------------------------------------

Add ioctl for users to obtain channel and release channel,
there are 2 types of channel:share_chns and non_share_chns.
Users are allowed to use parameter:share_chns to control
its number

Signed-off-by: default avatarzhangshuowen96 <zhangshuowen@hisilicon.com>
parent cbfdfeba
Loading
Loading
Loading
Loading
+66 −1
Original line number Diff line number Diff line
@@ -37,6 +37,62 @@ struct chn_ioe_info {
	u32 ch_cqe_status;
};

struct hisi_sdma_chn_num {
	u32 total_chn_num;
	u32 share_chn_num;
};

struct hisi_sdma_sq_entry {
	__le32 opcode          : 8;
	__le32 sssv            : 1;
	__le32 dssv            : 1;
	__le32 sns             : 1;
	__le32 dns             : 1;
	__le32 sro             : 1;
	__le32 dro             : 1;
	__le32 stride          : 2;
	__le32 ie              : 1;
	__le32 comp_en         : 1;
	__le32 reserved0       : 14;

	__le32 sqe_id          : 16;
	__le32 mpam_partid     : 8;
	__le32 mpamns          : 1;
	__le32 pmg             : 2;
	__le32 qos             : 4;
	__le32 reserved1       : 1;

	__le32 src_streamid    : 16;
	__le32 src_substreamid : 16;
	__le32 dst_streamid    : 16;
	__le32 dst_substreamid : 16;

	__le32 src_addr_l      : 32;
	__le32 src_addr_h      : 32;
	__le32 dst_addr_l      : 32;
	__le32 dst_addr_h      : 32;

	__le32 length_move     : 32;

	__le32 src_stride_len  : 32;
	__le32 dst_stride_len  : 32;
	__le32 stride_num      : 32;
	__le32 reserved2       : 32;
	__le32 reserved3       : 32;
	__le32 reserved4       : 32;
	__le32 reserved5       : 32;
};

struct hisi_sdma_cq_entry {
	__le32 reserved1;
	__le32 reserved2;
	__le32 sqhd      : 16;
	__le32 sqe_id    : 16;
	__le32 opcode    : 16;
	__le32 vld       : 1;
	__le32 status    : 15;
};

struct hisi_sdma_queue_info {
	u32    sq_head;
	u32    sq_tail;
@@ -51,6 +107,11 @@ struct hisi_sdma_queue_info {
	struct chn_ioe_info ioe;
};

struct hisi_sdma_share_chn {
	u16    chn_idx;
	bool   init_flag;
};

typedef int (*sdma_ioctl_funcs)(struct file *file, unsigned long arg);
struct hisi_sdma_ioctl_func_list {
	unsigned int cmd;
@@ -58,6 +119,10 @@ struct hisi_sdma_ioctl_func_list {
};

#define IOCTL_SDMA_GET_PROCESS_ID	_IOR('s', 1, u32)
#define IOCTL_SDMA_GET_STREAMID		_IOR('s', 2, u32)
#define IOCTL_SDMA_GET_CHN		_IOR('s', 2, int)
#define IOCTL_SDMA_PUT_CHN		_IOW('s', 3, int)
#define IOCTL_SDMA_GET_STREAMID		_IOR('s', 4, u32)
#define IOCTL_GET_SDMA_CHN_NUM		_IOR('s', 5, struct hisi_sdma_chn_num)
#define IOCTL_SDMA_CHN_USED_REFCOUNT	_IOW('s', 6, struct hisi_sdma_share_chn)

#endif
+291 −2
Original line number Diff line number Diff line
@@ -2,17 +2,25 @@
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/dma-iommu.h>
#include <linux/list.h>
#include <linux/platform_device.h>
#include <linux/sort.h>
#include "sdma_hal.h"

static struct hisi_sdma_global_info g_info;

struct hisi_sdma_channel_list {
	struct list_head chn_list;
	int chn_idx;
};

struct file_open_data {
	int ida;
	u32 pasid;
	struct iommu_sva *handle;
	struct hisi_sdma_device *psdma_dev;
	struct list_head non_share_chn_list;
	struct list_head share_chn_list;
};

static int __do_sdma_open(struct hisi_sdma_device *psdma_dev, struct file *file)
@@ -50,6 +58,9 @@ static int __do_sdma_open(struct hisi_sdma_device *psdma_dev, struct file *file)
	data->pasid = pasid;
	data->psdma_dev = psdma_dev;
	data->handle = handle;
	INIT_LIST_HEAD(&data->non_share_chn_list);
	INIT_LIST_HEAD(&data->share_chn_list);

	file->private_data = data;

	return 0;
@@ -84,10 +95,167 @@ static int ioctl_sdma_get_streamid(struct file *file, unsigned long arg)

	return 0;
}
static int ioctl_sdma_get_chn(struct file *file, unsigned long arg)
{
	struct file_open_data *data = file->private_data;
	struct hisi_sdma_device *pdev = data->psdma_dev;
	struct hisi_sdma_channel_list *list_node;
	u32 share_chns = *(g_info.share_chns);
	struct hisi_sdma_channel *pchannel;
	u32 alloc_chn_num_max, idx;
	int ret;

	list_node = kmalloc_node(sizeof(struct hisi_sdma_channel_list), GFP_KERNEL, pdev->node_idx);
	if (!list_node)
		return -ENOMEM;

	alloc_chn_num_max = pdev->nr_channel - share_chns;
	spin_lock(&pdev->channel_lock);
	idx = find_first_bit(pdev->channel_map, alloc_chn_num_max);
	if (idx != alloc_chn_num_max) {
		bitmap_clear(pdev->channel_map, idx, 1);
		pdev->nr_channel_used;
	} else {
		ret = -ENOSPC;
		goto unlock;
	}

	idx = share_chns;
	list_node->chn_idx = (int)idx;
	list_add(&list_node->chn_list, &data->non_share_chn_list);
	pchannel = pdev->channels  idx;
	pchannel->cnt_used;
	spin_unlock(&pdev->channel_lock);

	dev_dbg(&pdev->pdev->dev, "sdma get chn %u\n", idx);
	if (copy_to_user((int __user *)(uintptr_t)arg, &idx, sizeof(int))) {
		ret = -EFAULT;
		goto put_chn;
	}

	return 0;

put_chn:
	spin_lock(&pdev->channel_lock);
	list_del(&list_node->chn_list);
	bitmap_set(pdev->channel_map, idx - share_chns, 1);
	pdev->nr_channel_used--;
	pchannel->cnt_used--;
unlock:
	spin_unlock(&pdev->channel_lock);
	kfree(list_node);

	return ret;
}

static int ioctl_sdma_put_chn(struct file *file, unsigned long arg)
{
	struct file_open_data *data = file->private_data;
	struct hisi_sdma_device *pdev = data->psdma_dev;
	struct device *dev = &pdev->pdev->dev;
	u32 share_chns = *(g_info.share_chns);
	struct hisi_sdma_channel_list *c, *n;
	int idx;

	if (copy_from_user(&idx, (int __user *)(uintptr_t)arg, sizeof(int))) {
		dev_err(dev, "put user chn failed\n");
		return -EFAULT;
	}

	if (idx < (int)share_chns || idx >= (int)pdev->nr_channel) {
		dev_err(dev, "put idx = %d is err\n", idx);
		return -EFAULT;
	}

	spin_lock(&pdev->channel_lock);
	bitmap_set(pdev->channel_map, idx - share_chns, 1);
	pdev->nr_channel_used--;

	list_for_each_entry_safe(c, n, &data->non_share_chn_list, chn_list) {
		if (c->chn_idx == idx) {
			dev_dbg(dev, "sdma put chn %d\n", idx);
			list_del(&c->chn_list);
			break;
		}
	}

	spin_unlock(&pdev->channel_lock);

	return 0;
}

static int ioctl_get_sdma_chn_num(struct file *file, unsigned long arg)
{
	struct file_open_data *data = file->private_data;
	struct hisi_sdma_device *pdev = data->psdma_dev;
	struct hisi_sdma_chn_num chn_num;

	chn_num.total_chn_num = (u32)(pdev->nr_channel);
	chn_num.share_chn_num = *(g_info.share_chns);
	if (copy_to_user((struct hisi_sdma_chn_num __user *)(uintptr_t)arg, &chn_num,
			 sizeof(struct hisi_sdma_chn_num)))
		return -EFAULT;

	return 0;
}

static int ioctl_sdma_chn_used_refcount(struct file *file, unsigned long arg)
{
	struct file_open_data *data = file->private_data;
	struct hisi_sdma_device *pdev = data->psdma_dev;
	struct hisi_sdma_channel_list *list_node;
	struct device *dev = &pdev->pdev->dev;
	u32 share_chns = *(g_info.share_chns);
	struct hisi_sdma_share_chn share_chn;
	struct hisi_sdma_channel *pchannel;
	struct hisi_sdma_channel_list *c;
	struct hisi_sdma_channel_list *n;

	if (copy_from_user(&share_chn, (struct hisi_sdma_share_chn __user *)(uintptr_t)arg,
			   sizeof(struct hisi_sdma_share_chn))) {
		dev_err(dev, "get share chn failed\n");
		return -EFAULT;
	}
	if (share_chn.chn_idx >= share_chns) {
		dev_err(dev, "get share chn index = %u is err\n", share_chn.chn_idx);
		return -EFAULT;
	}

	spin_lock(&pdev->channel_lock);
	pchannel = pdev->channels  share_chn.chn_idx;
	if (share_chn.init_flag) {
		list_node = kmalloc_node(sizeof(struct hisi_sdma_channel_list), GFP_KERNEL,
					 pdev->node_idx);
		if (!list_node) {
			spin_unlock(&pdev->channel_lock);
			return -ENOMEM;
		}
		list_node->chn_idx = share_chn.chn_idx;
		list_add(&list_node->chn_list, &data->share_chn_list);
		pchannel->cnt_used;
	}
	if (!share_chn.init_flag && pchannel->cnt_used > 0) {
		list_for_each_entry_safe(c, n, &data->share_chn_list, chn_list) {
			if (c->chn_idx == share_chn.chn_idx) {
				dev_dbg(dev, "release share_chn%d\n", c->chn_idx);
				list_del(&c->chn_list);
				break;
			}
		}
		pchannel->cnt_used--;
	}
	spin_unlock(&pdev->channel_lock);

	return 0;
}

struct hisi_sdma_ioctl_func_list g_ioctl_funcs[] = {
	{IOCTL_SDMA_GET_PROCESS_ID,        ioctl_sdma_get_process_id},
	{IOCTL_SDMA_GET_CHN,               ioctl_sdma_get_chn},
	{IOCTL_SDMA_PUT_CHN,               ioctl_sdma_put_chn},
	{IOCTL_SDMA_GET_STREAMID,          ioctl_sdma_get_streamid},
	{IOCTL_GET_SDMA_CHN_NUM,           ioctl_get_sdma_chn_num},
	{IOCTL_SDMA_CHN_USED_REFCOUNT,     ioctl_sdma_chn_used_refcount},
};

static long sdma_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
@@ -124,9 +292,53 @@ static int sdma_core_open(struct inode *inode, struct file *file)
	return __do_sdma_open(psdma_dev, file);
}

ssize_t sdma_read_info(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	struct file_open_data *data = file->private_data;
	struct hisi_sdma_device *pdev = data->psdma_dev;
	struct device *dev = &pdev->pdev->dev;
	u32 share_chns = *(g_info.share_chns);

	if (share_chns > pdev->nr_channel)
		share_chns = pdev->nr_channel;
	dev_info(dev, "sdma%u has %u channels in total, %u share_channels\n",
		 pdev->idx, pdev->nr_channel, share_chns);

	return 0;
}

static int sdma_dev_release(struct inode *inode, struct file *file)
{
	struct file_open_data *data = file->private_data;
	struct hisi_sdma_device *pdev = data->psdma_dev;
	struct device *dev = &pdev->pdev->dev;
	u32 share_chns = *(g_info.share_chns);
	struct hisi_sdma_channel *pchannel;
	struct hisi_sdma_channel_list *c;
	struct hisi_sdma_channel_list *n;
	u32 pid = (u32)current->tgid;

	spin_lock(&pdev->channel_lock);
	list_for_each_entry_safe(c, n, &data->non_share_chn_list, chn_list) {
		dev_dbg(dev, "release non_share_chn%d\n", c->chn_idx);
		bitmap_set(pdev->channel_map, c->chn_idx - share_chns, 1);
		list_del(&c->chn_list);
		pdev->nr_channel_used--;
	}

	list_for_each_entry_safe(c, n, &data->share_chn_list, chn_list) {
		dev_dbg(dev, "release share_chn%d\n", c->chn_idx);
		pchannel = pdev->channels  c->chn_idx;
		pchannel->cnt_used--;
		if (pchannel->sync_info_base->lock != 0 &&
			pchannel->sync_info_base->lock_pid == (u32)current->tgid) {
			dev_err(dev, "process %d exit with lock\n", current->tgid);
			pchannel->sync_info_base->lock = 0;
			pchannel->sync_info_base->lock_pid = 0;
		}
		list_del(&c->chn_list);
	}
	spin_unlock(&pdev->channel_lock);

	if (data->handle) {
		iommu_sva_unbind_device(data->handle);
@@ -134,17 +346,93 @@ static int sdma_dev_release(struct inode *inode, struct file *file)
	}

	ida_free(g_info.fd_ida, data->ida);

	kfree(file->private_data);
	return 0;
}

static int remap_addr_range(u32 chn_num, u64 offset)
{
	if (offset >= chn_num * (HISI_SDMA_MMAP_SHMEM + 1))
		return -EINVAL;

	if (offset < chn_num * HISI_SDMA_MMAP_CQE)
		return HISI_SDMA_MMAP_SQE;

	else if (offset < chn_num * HISI_SDMA_MMAP_IO)
		return HISI_SDMA_MMAP_CQE;
	else if (offset < chn_num * HISI_SDMA_MMAP_SHMEM)
		return HISI_SDMA_MMAP_IO;
	else
		return HISI_SDMA_MMAP_SHMEM;
}

static int sdma_dev_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct file_open_data *data = file->private_data;
	struct hisi_sdma_channel *chn_base, *pchan;
	u64 io_base, size, offset, pfn_start;
	struct device *dev;
	u32 chn_num;
	int ret;

	chn_base = data->psdma_dev->channels;
	dev = &data->psdma_dev->pdev->dev;
	chn_num = chn_base->pdev->nr_channel;
	io_base = data->psdma_dev->base_addr;
	size = vma->vm_end - vma->vm_start;
	offset = vma->vm_pgoff;

	dev_dbg(dev, "sdma total channel num = %u, user mmap offset = 0x%llx", chn_num, offset);

	switch (remap_addr_range(chn_num, offset)) {
	case HISI_SDMA_MMAP_SQE:
		pchan = chn_base + offset;
		pfn_start = virt_to_phys(pchan->sq_base) >> PAGE_SHIFT;
		ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size,
				      vma->vm_page_prot);
		break;

	case HISI_SDMA_MMAP_CQE:
		pchan = chn_base + offset - chn_num * HISI_SDMA_MMAP_CQE;
		pfn_start = virt_to_phys(pchan->cq_base) >> PAGE_SHIFT;
		ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size,
				      vma->vm_page_prot);
		break;

	case HISI_SDMA_MMAP_IO:
		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
		pfn_start = (io_base + HISI_SDMA_CH_OFFSET) >> PAGE_SHIFT;
		pfn_start += (offset - chn_num * HISI_SDMA_MMAP_IO) * HISI_SDMA_REG_SIZE /
			      PAGE_SIZE;
		ret = io_remap_pfn_range(vma, vma->vm_start, pfn_start, size,
					 vma->vm_page_prot);
		break;

	case HISI_SDMA_MMAP_SHMEM:
		pchan = chn_base + offset - chn_num * HISI_SDMA_MMAP_SHMEM;
		pfn_start = virt_to_phys(pchan->sync_info_base) >> PAGE_SHIFT;
		ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size,
				      vma->vm_page_prot);
		break;

	default:
		return -EINVAL;
	}

	if (ret)
		dev_err(dev, "sdma mmap failed!\n");

	return ret;
}

static const struct file_operations sdma_core_fops = {
	.owner = THIS_MODULE,
	.open = sdma_core_open,
	.read = NULL,
	.read = sdma_read_info,
	.release = sdma_dev_release,
	.unlocked_ioctl = sdma_dev_ioctl,
	.mmap = NULL,
	.mmap = sdma_dev_mmap,
};

void sdma_cdev_init(struct cdev *cdev)
@@ -157,4 +445,5 @@ void sdma_info_sync_cdev(struct hisi_sdma_global_info *g_info_input)
{
	g_info.core_dev = g_info_input->core_dev;
	g_info.fd_ida = g_info_input->fd_ida;
	g_info.share_chns = g_info_input->share_chns;
}
+4 −1
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@
#include "hisi_sdma.h"
#include "sdma_reg.h"

+#define RW_R_R			0644

#define sdma_wmb() (asm volatile("dsb st" ::: "memory"))

/**
@@ -36,7 +38,6 @@ struct hisi_sdma_channel {
	u16 cnt_used;
};


/**
 * struct hisi_sdma_device - SDMA device structure
 * @idx: SDMA device's ID
@@ -80,6 +81,7 @@ struct hisi_sdma_core_device {
};

struct hisi_sdma_global_info {
	u32 *share_chns;
	struct hisi_sdma_core_device *core_dev;
	struct ida *fd_ida;
};
@@ -93,6 +95,7 @@ static inline void chn_set_val(struct hisi_sdma_channel *pchan, int reg, u32 val

	reg_val &= ~mask;
	reg_val |= FIELD_PREP(mask, val);
	sdma_wmb();

	writel(reg_val, pchan->io_base  reg);
}
+10 −0
Original line number Diff line number Diff line
@@ -13,6 +13,10 @@
#define UPPER_SHIFT		32
#define MAX_INPUT_LENGTH	128

u32 share_chns = 16;
module_param(share_chns, uint, RW_R_R);
MODULE_PARM_DESC(share_chns, "num of share channels, 16 by default");

struct ida fd_ida;
struct hisi_sdma_core_device hisi_sdma_core_device = {0};
static struct class *sdma_class;
@@ -174,6 +178,11 @@ int sdma_init_channels(struct hisi_sdma_device *psdma_dev)
	}

	bitmap_set(psdma_dev->channel_map, 0, chn_num);
	if (share_chns > chn_num) {
		dev_warn(&psdma_dev->pdev->dev, "share_chns max val = %u!\n", chn_num);
		share_chns = chn_num;
	}
	bitmap_set(psdma_dev->channel_map, 0, chn_num - share_chns);

	return 0;

@@ -413,6 +422,7 @@ static void global_var_init(struct hisi_sdma_global_info *g_info)
	ida_init(&fd_ida);

	g_info->core_dev = &hisi_sdma_core_device;
	g_info->share_chns = &share_chns;
	g_info->fd_ida = &fd_ida;
}