18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * cdev.c - Character device component for Mostcore
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2013-2015 Microchip Technology Germany II GmbH & Co. KG
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/sched.h>
108c2ecf20Sopenharmony_ci#include <linux/fs.h>
118c2ecf20Sopenharmony_ci#include <linux/slab.h>
128c2ecf20Sopenharmony_ci#include <linux/device.h>
138c2ecf20Sopenharmony_ci#include <linux/cdev.h>
148c2ecf20Sopenharmony_ci#include <linux/poll.h>
158c2ecf20Sopenharmony_ci#include <linux/kfifo.h>
168c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
178c2ecf20Sopenharmony_ci#include <linux/idr.h>
188c2ecf20Sopenharmony_ci#include <linux/most.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define CHRDEV_REGION_SIZE 50
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistatic struct cdev_component {
238c2ecf20Sopenharmony_ci	dev_t devno;
248c2ecf20Sopenharmony_ci	struct ida minor_id;
258c2ecf20Sopenharmony_ci	unsigned int major;
268c2ecf20Sopenharmony_ci	struct class *class;
278c2ecf20Sopenharmony_ci	struct most_component cc;
288c2ecf20Sopenharmony_ci} comp;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistruct comp_channel {
318c2ecf20Sopenharmony_ci	wait_queue_head_t wq;
328c2ecf20Sopenharmony_ci	spinlock_t unlink;	/* synchronization lock to unlink channels */
338c2ecf20Sopenharmony_ci	struct cdev cdev;
348c2ecf20Sopenharmony_ci	struct device *dev;
358c2ecf20Sopenharmony_ci	struct mutex io_mutex;
368c2ecf20Sopenharmony_ci	struct most_interface *iface;
378c2ecf20Sopenharmony_ci	struct most_channel_config *cfg;
388c2ecf20Sopenharmony_ci	unsigned int channel_id;
398c2ecf20Sopenharmony_ci	dev_t devno;
408c2ecf20Sopenharmony_ci	size_t mbo_offs;
418c2ecf20Sopenharmony_ci	DECLARE_KFIFO_PTR(fifo, typeof(struct mbo *));
428c2ecf20Sopenharmony_ci	int access_ref;
438c2ecf20Sopenharmony_ci	struct list_head list;
448c2ecf20Sopenharmony_ci};
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci#define to_channel(d) container_of(d, struct comp_channel, cdev)
478c2ecf20Sopenharmony_cistatic struct list_head channel_list;
488c2ecf20Sopenharmony_cistatic spinlock_t ch_list_lock;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic inline bool ch_has_mbo(struct comp_channel *c)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	return channel_has_mbo(c->iface, c->channel_id, &comp.cc) > 0;
538c2ecf20Sopenharmony_ci}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic inline struct mbo *ch_get_mbo(struct comp_channel *c, struct mbo **mbo)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	if (!kfifo_peek(&c->fifo, mbo)) {
588c2ecf20Sopenharmony_ci		*mbo = most_get_mbo(c->iface, c->channel_id, &comp.cc);
598c2ecf20Sopenharmony_ci		if (*mbo)
608c2ecf20Sopenharmony_ci			kfifo_in(&c->fifo, mbo, 1);
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci	return *mbo;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic struct comp_channel *get_channel(struct most_interface *iface, int id)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	struct comp_channel *c, *tmp;
688c2ecf20Sopenharmony_ci	unsigned long flags;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ch_list_lock, flags);
718c2ecf20Sopenharmony_ci	list_for_each_entry_safe(c, tmp, &channel_list, list) {
728c2ecf20Sopenharmony_ci		if ((c->iface == iface) && (c->channel_id == id)) {
738c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&ch_list_lock, flags);
748c2ecf20Sopenharmony_ci			return c;
758c2ecf20Sopenharmony_ci		}
768c2ecf20Sopenharmony_ci	}
778c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ch_list_lock, flags);
788c2ecf20Sopenharmony_ci	return NULL;
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic void stop_channel(struct comp_channel *c)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct mbo *mbo;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	while (kfifo_out((struct kfifo *)&c->fifo, &mbo, 1))
868c2ecf20Sopenharmony_ci		most_put_mbo(mbo);
878c2ecf20Sopenharmony_ci	most_stop_channel(c->iface, c->channel_id, &comp.cc);
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic void destroy_cdev(struct comp_channel *c)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	unsigned long flags;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	device_destroy(comp.class, c->devno);
958c2ecf20Sopenharmony_ci	cdev_del(&c->cdev);
968c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ch_list_lock, flags);
978c2ecf20Sopenharmony_ci	list_del(&c->list);
988c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ch_list_lock, flags);
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic void destroy_channel(struct comp_channel *c)
1028c2ecf20Sopenharmony_ci{
1038c2ecf20Sopenharmony_ci	ida_simple_remove(&comp.minor_id, MINOR(c->devno));
1048c2ecf20Sopenharmony_ci	kfifo_free(&c->fifo);
1058c2ecf20Sopenharmony_ci	kfree(c);
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci/**
1098c2ecf20Sopenharmony_ci * comp_open - implements the syscall to open the device
1108c2ecf20Sopenharmony_ci * @inode: inode pointer
1118c2ecf20Sopenharmony_ci * @filp: file pointer
1128c2ecf20Sopenharmony_ci *
1138c2ecf20Sopenharmony_ci * This stores the channel pointer in the private data field of
1148c2ecf20Sopenharmony_ci * the file structure and activates the channel within the core.
1158c2ecf20Sopenharmony_ci */
1168c2ecf20Sopenharmony_cistatic int comp_open(struct inode *inode, struct file *filp)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	struct comp_channel *c;
1198c2ecf20Sopenharmony_ci	int ret;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	c = to_channel(inode->i_cdev);
1228c2ecf20Sopenharmony_ci	filp->private_data = c;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (((c->cfg->direction == MOST_CH_RX) &&
1258c2ecf20Sopenharmony_ci	     ((filp->f_flags & O_ACCMODE) != O_RDONLY)) ||
1268c2ecf20Sopenharmony_ci	     ((c->cfg->direction == MOST_CH_TX) &&
1278c2ecf20Sopenharmony_ci		((filp->f_flags & O_ACCMODE) != O_WRONLY))) {
1288c2ecf20Sopenharmony_ci		return -EACCES;
1298c2ecf20Sopenharmony_ci	}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	mutex_lock(&c->io_mutex);
1328c2ecf20Sopenharmony_ci	if (!c->dev) {
1338c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
1348c2ecf20Sopenharmony_ci		return -ENODEV;
1358c2ecf20Sopenharmony_ci	}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	if (c->access_ref) {
1388c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
1398c2ecf20Sopenharmony_ci		return -EBUSY;
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	c->mbo_offs = 0;
1438c2ecf20Sopenharmony_ci	ret = most_start_channel(c->iface, c->channel_id, &comp.cc);
1448c2ecf20Sopenharmony_ci	if (!ret)
1458c2ecf20Sopenharmony_ci		c->access_ref = 1;
1468c2ecf20Sopenharmony_ci	mutex_unlock(&c->io_mutex);
1478c2ecf20Sopenharmony_ci	return ret;
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci/**
1518c2ecf20Sopenharmony_ci * comp_close - implements the syscall to close the device
1528c2ecf20Sopenharmony_ci * @inode: inode pointer
1538c2ecf20Sopenharmony_ci * @filp: file pointer
1548c2ecf20Sopenharmony_ci *
1558c2ecf20Sopenharmony_ci * This stops the channel within the core.
1568c2ecf20Sopenharmony_ci */
1578c2ecf20Sopenharmony_cistatic int comp_close(struct inode *inode, struct file *filp)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	struct comp_channel *c = to_channel(inode->i_cdev);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	mutex_lock(&c->io_mutex);
1628c2ecf20Sopenharmony_ci	spin_lock(&c->unlink);
1638c2ecf20Sopenharmony_ci	c->access_ref = 0;
1648c2ecf20Sopenharmony_ci	spin_unlock(&c->unlink);
1658c2ecf20Sopenharmony_ci	if (c->dev) {
1668c2ecf20Sopenharmony_ci		stop_channel(c);
1678c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
1688c2ecf20Sopenharmony_ci	} else {
1698c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
1708c2ecf20Sopenharmony_ci		destroy_channel(c);
1718c2ecf20Sopenharmony_ci	}
1728c2ecf20Sopenharmony_ci	return 0;
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci/**
1768c2ecf20Sopenharmony_ci * comp_write - implements the syscall to write to the device
1778c2ecf20Sopenharmony_ci * @filp: file pointer
1788c2ecf20Sopenharmony_ci * @buf: pointer to user buffer
1798c2ecf20Sopenharmony_ci * @count: number of bytes to write
1808c2ecf20Sopenharmony_ci * @offset: offset from where to start writing
1818c2ecf20Sopenharmony_ci */
1828c2ecf20Sopenharmony_cistatic ssize_t comp_write(struct file *filp, const char __user *buf,
1838c2ecf20Sopenharmony_ci			  size_t count, loff_t *offset)
1848c2ecf20Sopenharmony_ci{
1858c2ecf20Sopenharmony_ci	int ret;
1868c2ecf20Sopenharmony_ci	size_t to_copy, left;
1878c2ecf20Sopenharmony_ci	struct mbo *mbo = NULL;
1888c2ecf20Sopenharmony_ci	struct comp_channel *c = filp->private_data;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	mutex_lock(&c->io_mutex);
1918c2ecf20Sopenharmony_ci	while (c->dev && !ch_get_mbo(c, &mbo)) {
1928c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci		if ((filp->f_flags & O_NONBLOCK))
1958c2ecf20Sopenharmony_ci			return -EAGAIN;
1968c2ecf20Sopenharmony_ci		if (wait_event_interruptible(c->wq, ch_has_mbo(c) || !c->dev))
1978c2ecf20Sopenharmony_ci			return -ERESTARTSYS;
1988c2ecf20Sopenharmony_ci		mutex_lock(&c->io_mutex);
1998c2ecf20Sopenharmony_ci	}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	if (unlikely(!c->dev)) {
2028c2ecf20Sopenharmony_ci		ret = -ENODEV;
2038c2ecf20Sopenharmony_ci		goto unlock;
2048c2ecf20Sopenharmony_ci	}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	to_copy = min(count, c->cfg->buffer_size - c->mbo_offs);
2078c2ecf20Sopenharmony_ci	left = copy_from_user(mbo->virt_address + c->mbo_offs, buf, to_copy);
2088c2ecf20Sopenharmony_ci	if (left == to_copy) {
2098c2ecf20Sopenharmony_ci		ret = -EFAULT;
2108c2ecf20Sopenharmony_ci		goto unlock;
2118c2ecf20Sopenharmony_ci	}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	c->mbo_offs += to_copy - left;
2148c2ecf20Sopenharmony_ci	if (c->mbo_offs >= c->cfg->buffer_size ||
2158c2ecf20Sopenharmony_ci	    c->cfg->data_type == MOST_CH_CONTROL ||
2168c2ecf20Sopenharmony_ci	    c->cfg->data_type == MOST_CH_ASYNC) {
2178c2ecf20Sopenharmony_ci		kfifo_skip(&c->fifo);
2188c2ecf20Sopenharmony_ci		mbo->buffer_length = c->mbo_offs;
2198c2ecf20Sopenharmony_ci		c->mbo_offs = 0;
2208c2ecf20Sopenharmony_ci		most_submit_mbo(mbo);
2218c2ecf20Sopenharmony_ci	}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	ret = to_copy - left;
2248c2ecf20Sopenharmony_ciunlock:
2258c2ecf20Sopenharmony_ci	mutex_unlock(&c->io_mutex);
2268c2ecf20Sopenharmony_ci	return ret;
2278c2ecf20Sopenharmony_ci}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci/**
2308c2ecf20Sopenharmony_ci * comp_read - implements the syscall to read from the device
2318c2ecf20Sopenharmony_ci * @filp: file pointer
2328c2ecf20Sopenharmony_ci * @buf: pointer to user buffer
2338c2ecf20Sopenharmony_ci * @count: number of bytes to read
2348c2ecf20Sopenharmony_ci * @offset: offset from where to start reading
2358c2ecf20Sopenharmony_ci */
2368c2ecf20Sopenharmony_cistatic ssize_t
2378c2ecf20Sopenharmony_cicomp_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
2388c2ecf20Sopenharmony_ci{
2398c2ecf20Sopenharmony_ci	size_t to_copy, not_copied, copied;
2408c2ecf20Sopenharmony_ci	struct mbo *mbo = NULL;
2418c2ecf20Sopenharmony_ci	struct comp_channel *c = filp->private_data;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	mutex_lock(&c->io_mutex);
2448c2ecf20Sopenharmony_ci	while (c->dev && !kfifo_peek(&c->fifo, &mbo)) {
2458c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
2468c2ecf20Sopenharmony_ci		if (filp->f_flags & O_NONBLOCK)
2478c2ecf20Sopenharmony_ci			return -EAGAIN;
2488c2ecf20Sopenharmony_ci		if (wait_event_interruptible(c->wq,
2498c2ecf20Sopenharmony_ci					     (!kfifo_is_empty(&c->fifo) ||
2508c2ecf20Sopenharmony_ci					      (!c->dev))))
2518c2ecf20Sopenharmony_ci			return -ERESTARTSYS;
2528c2ecf20Sopenharmony_ci		mutex_lock(&c->io_mutex);
2538c2ecf20Sopenharmony_ci	}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	/* make sure we don't submit to gone devices */
2568c2ecf20Sopenharmony_ci	if (unlikely(!c->dev)) {
2578c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
2588c2ecf20Sopenharmony_ci		return -ENODEV;
2598c2ecf20Sopenharmony_ci	}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	to_copy = min_t(size_t,
2628c2ecf20Sopenharmony_ci			count,
2638c2ecf20Sopenharmony_ci			mbo->processed_length - c->mbo_offs);
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	not_copied = copy_to_user(buf,
2668c2ecf20Sopenharmony_ci				  mbo->virt_address + c->mbo_offs,
2678c2ecf20Sopenharmony_ci				  to_copy);
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	copied = to_copy - not_copied;
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	c->mbo_offs += copied;
2728c2ecf20Sopenharmony_ci	if (c->mbo_offs >= mbo->processed_length) {
2738c2ecf20Sopenharmony_ci		kfifo_skip(&c->fifo);
2748c2ecf20Sopenharmony_ci		most_put_mbo(mbo);
2758c2ecf20Sopenharmony_ci		c->mbo_offs = 0;
2768c2ecf20Sopenharmony_ci	}
2778c2ecf20Sopenharmony_ci	mutex_unlock(&c->io_mutex);
2788c2ecf20Sopenharmony_ci	return copied;
2798c2ecf20Sopenharmony_ci}
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_cistatic __poll_t comp_poll(struct file *filp, poll_table *wait)
2828c2ecf20Sopenharmony_ci{
2838c2ecf20Sopenharmony_ci	struct comp_channel *c = filp->private_data;
2848c2ecf20Sopenharmony_ci	__poll_t mask = 0;
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	poll_wait(filp, &c->wq, wait);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	mutex_lock(&c->io_mutex);
2898c2ecf20Sopenharmony_ci	if (c->cfg->direction == MOST_CH_RX) {
2908c2ecf20Sopenharmony_ci		if (!c->dev || !kfifo_is_empty(&c->fifo))
2918c2ecf20Sopenharmony_ci			mask |= EPOLLIN | EPOLLRDNORM;
2928c2ecf20Sopenharmony_ci	} else {
2938c2ecf20Sopenharmony_ci		if (!c->dev || !kfifo_is_empty(&c->fifo) || ch_has_mbo(c))
2948c2ecf20Sopenharmony_ci			mask |= EPOLLOUT | EPOLLWRNORM;
2958c2ecf20Sopenharmony_ci	}
2968c2ecf20Sopenharmony_ci	mutex_unlock(&c->io_mutex);
2978c2ecf20Sopenharmony_ci	return mask;
2988c2ecf20Sopenharmony_ci}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci/**
3018c2ecf20Sopenharmony_ci * Initialization of struct file_operations
3028c2ecf20Sopenharmony_ci */
3038c2ecf20Sopenharmony_cistatic const struct file_operations channel_fops = {
3048c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
3058c2ecf20Sopenharmony_ci	.read = comp_read,
3068c2ecf20Sopenharmony_ci	.write = comp_write,
3078c2ecf20Sopenharmony_ci	.open = comp_open,
3088c2ecf20Sopenharmony_ci	.release = comp_close,
3098c2ecf20Sopenharmony_ci	.poll = comp_poll,
3108c2ecf20Sopenharmony_ci};
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci/**
3138c2ecf20Sopenharmony_ci * comp_disconnect_channel - disconnect a channel
3148c2ecf20Sopenharmony_ci * @iface: pointer to interface instance
3158c2ecf20Sopenharmony_ci * @channel_id: channel index
3168c2ecf20Sopenharmony_ci *
3178c2ecf20Sopenharmony_ci * This frees allocated memory and removes the cdev that represents this
3188c2ecf20Sopenharmony_ci * channel in user space.
3198c2ecf20Sopenharmony_ci */
3208c2ecf20Sopenharmony_cistatic int comp_disconnect_channel(struct most_interface *iface, int channel_id)
3218c2ecf20Sopenharmony_ci{
3228c2ecf20Sopenharmony_ci	struct comp_channel *c;
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci	c = get_channel(iface, channel_id);
3258c2ecf20Sopenharmony_ci	if (!c)
3268c2ecf20Sopenharmony_ci		return -EINVAL;
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	mutex_lock(&c->io_mutex);
3298c2ecf20Sopenharmony_ci	spin_lock(&c->unlink);
3308c2ecf20Sopenharmony_ci	c->dev = NULL;
3318c2ecf20Sopenharmony_ci	spin_unlock(&c->unlink);
3328c2ecf20Sopenharmony_ci	destroy_cdev(c);
3338c2ecf20Sopenharmony_ci	if (c->access_ref) {
3348c2ecf20Sopenharmony_ci		stop_channel(c);
3358c2ecf20Sopenharmony_ci		wake_up_interruptible(&c->wq);
3368c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
3378c2ecf20Sopenharmony_ci	} else {
3388c2ecf20Sopenharmony_ci		mutex_unlock(&c->io_mutex);
3398c2ecf20Sopenharmony_ci		destroy_channel(c);
3408c2ecf20Sopenharmony_ci	}
3418c2ecf20Sopenharmony_ci	return 0;
3428c2ecf20Sopenharmony_ci}
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci/**
3458c2ecf20Sopenharmony_ci * comp_rx_completion - completion handler for rx channels
3468c2ecf20Sopenharmony_ci * @mbo: pointer to buffer object that has completed
3478c2ecf20Sopenharmony_ci *
3488c2ecf20Sopenharmony_ci * This searches for the channel linked to this MBO and stores it in the local
3498c2ecf20Sopenharmony_ci * fifo buffer.
3508c2ecf20Sopenharmony_ci */
3518c2ecf20Sopenharmony_cistatic int comp_rx_completion(struct mbo *mbo)
3528c2ecf20Sopenharmony_ci{
3538c2ecf20Sopenharmony_ci	struct comp_channel *c;
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci	if (!mbo)
3568c2ecf20Sopenharmony_ci		return -EINVAL;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	c = get_channel(mbo->ifp, mbo->hdm_channel_id);
3598c2ecf20Sopenharmony_ci	if (!c)
3608c2ecf20Sopenharmony_ci		return -EINVAL;
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	spin_lock(&c->unlink);
3638c2ecf20Sopenharmony_ci	if (!c->access_ref || !c->dev) {
3648c2ecf20Sopenharmony_ci		spin_unlock(&c->unlink);
3658c2ecf20Sopenharmony_ci		return -ENODEV;
3668c2ecf20Sopenharmony_ci	}
3678c2ecf20Sopenharmony_ci	kfifo_in(&c->fifo, &mbo, 1);
3688c2ecf20Sopenharmony_ci	spin_unlock(&c->unlink);
3698c2ecf20Sopenharmony_ci#ifdef DEBUG_MESG
3708c2ecf20Sopenharmony_ci	if (kfifo_is_full(&c->fifo))
3718c2ecf20Sopenharmony_ci		dev_warn(c->dev, "Fifo is full\n");
3728c2ecf20Sopenharmony_ci#endif
3738c2ecf20Sopenharmony_ci	wake_up_interruptible(&c->wq);
3748c2ecf20Sopenharmony_ci	return 0;
3758c2ecf20Sopenharmony_ci}
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci/**
3788c2ecf20Sopenharmony_ci * comp_tx_completion - completion handler for tx channels
3798c2ecf20Sopenharmony_ci * @iface: pointer to interface instance
3808c2ecf20Sopenharmony_ci * @channel_id: channel index/ID
3818c2ecf20Sopenharmony_ci *
3828c2ecf20Sopenharmony_ci * This wakes sleeping processes in the wait-queue.
3838c2ecf20Sopenharmony_ci */
3848c2ecf20Sopenharmony_cistatic int comp_tx_completion(struct most_interface *iface, int channel_id)
3858c2ecf20Sopenharmony_ci{
3868c2ecf20Sopenharmony_ci	struct comp_channel *c;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci	c = get_channel(iface, channel_id);
3898c2ecf20Sopenharmony_ci	if (!c)
3908c2ecf20Sopenharmony_ci		return -EINVAL;
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	if ((channel_id < 0) || (channel_id >= iface->num_channels)) {
3938c2ecf20Sopenharmony_ci		dev_warn(c->dev, "Channel ID out of range\n");
3948c2ecf20Sopenharmony_ci		return -EINVAL;
3958c2ecf20Sopenharmony_ci	}
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci	wake_up_interruptible(&c->wq);
3988c2ecf20Sopenharmony_ci	return 0;
3998c2ecf20Sopenharmony_ci}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci/**
4028c2ecf20Sopenharmony_ci * comp_probe - probe function of the driver module
4038c2ecf20Sopenharmony_ci * @iface: pointer to interface instance
4048c2ecf20Sopenharmony_ci * @channel_id: channel index/ID
4058c2ecf20Sopenharmony_ci * @cfg: pointer to actual channel configuration
4068c2ecf20Sopenharmony_ci * @name: name of the device to be created
4078c2ecf20Sopenharmony_ci *
4088c2ecf20Sopenharmony_ci * This allocates achannel object and creates the device node in /dev
4098c2ecf20Sopenharmony_ci *
4108c2ecf20Sopenharmony_ci * Returns 0 on success or error code otherwise.
4118c2ecf20Sopenharmony_ci */
4128c2ecf20Sopenharmony_cistatic int comp_probe(struct most_interface *iface, int channel_id,
4138c2ecf20Sopenharmony_ci		      struct most_channel_config *cfg, char *name, char *args)
4148c2ecf20Sopenharmony_ci{
4158c2ecf20Sopenharmony_ci	struct comp_channel *c;
4168c2ecf20Sopenharmony_ci	unsigned long cl_flags;
4178c2ecf20Sopenharmony_ci	int retval;
4188c2ecf20Sopenharmony_ci	int current_minor;
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	if (!cfg || !name)
4218c2ecf20Sopenharmony_ci		return -EINVAL;
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci	c = get_channel(iface, channel_id);
4248c2ecf20Sopenharmony_ci	if (c)
4258c2ecf20Sopenharmony_ci		return -EEXIST;
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	current_minor = ida_simple_get(&comp.minor_id, 0, 0, GFP_KERNEL);
4288c2ecf20Sopenharmony_ci	if (current_minor < 0)
4298c2ecf20Sopenharmony_ci		return current_minor;
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	c = kzalloc(sizeof(*c), GFP_KERNEL);
4328c2ecf20Sopenharmony_ci	if (!c) {
4338c2ecf20Sopenharmony_ci		retval = -ENOMEM;
4348c2ecf20Sopenharmony_ci		goto err_remove_ida;
4358c2ecf20Sopenharmony_ci	}
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	c->devno = MKDEV(comp.major, current_minor);
4388c2ecf20Sopenharmony_ci	cdev_init(&c->cdev, &channel_fops);
4398c2ecf20Sopenharmony_ci	c->cdev.owner = THIS_MODULE;
4408c2ecf20Sopenharmony_ci	retval = cdev_add(&c->cdev, c->devno, 1);
4418c2ecf20Sopenharmony_ci	if (retval < 0)
4428c2ecf20Sopenharmony_ci		goto err_free_c;
4438c2ecf20Sopenharmony_ci	c->iface = iface;
4448c2ecf20Sopenharmony_ci	c->cfg = cfg;
4458c2ecf20Sopenharmony_ci	c->channel_id = channel_id;
4468c2ecf20Sopenharmony_ci	c->access_ref = 0;
4478c2ecf20Sopenharmony_ci	spin_lock_init(&c->unlink);
4488c2ecf20Sopenharmony_ci	INIT_KFIFO(c->fifo);
4498c2ecf20Sopenharmony_ci	retval = kfifo_alloc(&c->fifo, cfg->num_buffers, GFP_KERNEL);
4508c2ecf20Sopenharmony_ci	if (retval)
4518c2ecf20Sopenharmony_ci		goto err_del_cdev_and_free_channel;
4528c2ecf20Sopenharmony_ci	init_waitqueue_head(&c->wq);
4538c2ecf20Sopenharmony_ci	mutex_init(&c->io_mutex);
4548c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ch_list_lock, cl_flags);
4558c2ecf20Sopenharmony_ci	list_add_tail(&c->list, &channel_list);
4568c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ch_list_lock, cl_flags);
4578c2ecf20Sopenharmony_ci	c->dev = device_create(comp.class, NULL, c->devno, NULL, "%s", name);
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	if (IS_ERR(c->dev)) {
4608c2ecf20Sopenharmony_ci		retval = PTR_ERR(c->dev);
4618c2ecf20Sopenharmony_ci		goto err_free_kfifo_and_del_list;
4628c2ecf20Sopenharmony_ci	}
4638c2ecf20Sopenharmony_ci	kobject_uevent(&c->dev->kobj, KOBJ_ADD);
4648c2ecf20Sopenharmony_ci	return 0;
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_cierr_free_kfifo_and_del_list:
4678c2ecf20Sopenharmony_ci	kfifo_free(&c->fifo);
4688c2ecf20Sopenharmony_ci	list_del(&c->list);
4698c2ecf20Sopenharmony_cierr_del_cdev_and_free_channel:
4708c2ecf20Sopenharmony_ci	cdev_del(&c->cdev);
4718c2ecf20Sopenharmony_cierr_free_c:
4728c2ecf20Sopenharmony_ci	kfree(c);
4738c2ecf20Sopenharmony_cierr_remove_ida:
4748c2ecf20Sopenharmony_ci	ida_simple_remove(&comp.minor_id, current_minor);
4758c2ecf20Sopenharmony_ci	return retval;
4768c2ecf20Sopenharmony_ci}
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_cistatic struct cdev_component comp = {
4798c2ecf20Sopenharmony_ci	.cc = {
4808c2ecf20Sopenharmony_ci		.mod = THIS_MODULE,
4818c2ecf20Sopenharmony_ci		.name = "cdev",
4828c2ecf20Sopenharmony_ci		.probe_channel = comp_probe,
4838c2ecf20Sopenharmony_ci		.disconnect_channel = comp_disconnect_channel,
4848c2ecf20Sopenharmony_ci		.rx_completion = comp_rx_completion,
4858c2ecf20Sopenharmony_ci		.tx_completion = comp_tx_completion,
4868c2ecf20Sopenharmony_ci	},
4878c2ecf20Sopenharmony_ci};
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_cistatic int __init mod_init(void)
4908c2ecf20Sopenharmony_ci{
4918c2ecf20Sopenharmony_ci	int err;
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci	comp.class = class_create(THIS_MODULE, "most_cdev");
4948c2ecf20Sopenharmony_ci	if (IS_ERR(comp.class))
4958c2ecf20Sopenharmony_ci		return PTR_ERR(comp.class);
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&channel_list);
4988c2ecf20Sopenharmony_ci	spin_lock_init(&ch_list_lock);
4998c2ecf20Sopenharmony_ci	ida_init(&comp.minor_id);
5008c2ecf20Sopenharmony_ci
5018c2ecf20Sopenharmony_ci	err = alloc_chrdev_region(&comp.devno, 0, CHRDEV_REGION_SIZE, "cdev");
5028c2ecf20Sopenharmony_ci	if (err < 0)
5038c2ecf20Sopenharmony_ci		goto dest_ida;
5048c2ecf20Sopenharmony_ci	comp.major = MAJOR(comp.devno);
5058c2ecf20Sopenharmony_ci	err = most_register_component(&comp.cc);
5068c2ecf20Sopenharmony_ci	if (err)
5078c2ecf20Sopenharmony_ci		goto free_cdev;
5088c2ecf20Sopenharmony_ci	err = most_register_configfs_subsys(&comp.cc);
5098c2ecf20Sopenharmony_ci	if (err)
5108c2ecf20Sopenharmony_ci		goto deregister_comp;
5118c2ecf20Sopenharmony_ci	return 0;
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_cideregister_comp:
5148c2ecf20Sopenharmony_ci	most_deregister_component(&comp.cc);
5158c2ecf20Sopenharmony_cifree_cdev:
5168c2ecf20Sopenharmony_ci	unregister_chrdev_region(comp.devno, CHRDEV_REGION_SIZE);
5178c2ecf20Sopenharmony_cidest_ida:
5188c2ecf20Sopenharmony_ci	ida_destroy(&comp.minor_id);
5198c2ecf20Sopenharmony_ci	class_destroy(comp.class);
5208c2ecf20Sopenharmony_ci	return err;
5218c2ecf20Sopenharmony_ci}
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_cistatic void __exit mod_exit(void)
5248c2ecf20Sopenharmony_ci{
5258c2ecf20Sopenharmony_ci	struct comp_channel *c, *tmp;
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	most_deregister_configfs_subsys(&comp.cc);
5288c2ecf20Sopenharmony_ci	most_deregister_component(&comp.cc);
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_ci	list_for_each_entry_safe(c, tmp, &channel_list, list) {
5318c2ecf20Sopenharmony_ci		destroy_cdev(c);
5328c2ecf20Sopenharmony_ci		destroy_channel(c);
5338c2ecf20Sopenharmony_ci	}
5348c2ecf20Sopenharmony_ci	unregister_chrdev_region(comp.devno, CHRDEV_REGION_SIZE);
5358c2ecf20Sopenharmony_ci	ida_destroy(&comp.minor_id);
5368c2ecf20Sopenharmony_ci	class_destroy(comp.class);
5378c2ecf20Sopenharmony_ci}
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_cimodule_init(mod_init);
5408c2ecf20Sopenharmony_cimodule_exit(mod_exit);
5418c2ecf20Sopenharmony_ciMODULE_AUTHOR("Christian Gromm <christian.gromm@microchip.com>");
5428c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
5438c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("character device component for mostcore");
544