546 lines · 12.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * cdev.c - Character device component for Mostcore * * Copyright (C) 2013-2015 Microchip Technology Germany II GmbH & Co. KG */ #include <linux/module.h>#include <linux/sched.h>#include <linux/fs.h>#include <linux/slab.h>#include <linux/device.h>#include <linux/cdev.h>#include <linux/poll.h>#include <linux/kfifo.h>#include <linux/uaccess.h>#include <linux/idr.h>#include <linux/most.h> #define CHRDEV_REGION_SIZE 50 static const struct class most_cdev_class = {	.name		= "most_cdev"}; static struct cdev_component {	dev_t devno;	struct ida minor_id;	unsigned int major;	struct most_component cc;} comp; struct comp_channel {	wait_queue_head_t wq;	spinlock_t unlink;	/* synchronization lock to unlink channels */	struct cdev cdev;	struct device *dev;	struct mutex io_mutex;	struct most_interface *iface;	struct most_channel_config *cfg;	unsigned int channel_id;	dev_t devno;	size_t mbo_offs;	DECLARE_KFIFO_PTR(fifo, typeof(struct mbo *));	int access_ref;	struct list_head list;}; #define to_channel(d) container_of(d, struct comp_channel, cdev)static LIST_HEAD(channel_list);static DEFINE_SPINLOCK(ch_list_lock); static inline bool ch_has_mbo(struct comp_channel *c){	return channel_has_mbo(c->iface, c->channel_id, &comp.cc) > 0;} static inline struct mbo *ch_get_mbo(struct comp_channel *c, struct mbo **mbo){	if (!kfifo_peek(&c->fifo, mbo)) {		*mbo = most_get_mbo(c->iface, c->channel_id, &comp.cc);		if (*mbo)			kfifo_in(&c->fifo, mbo, 1);	}	return *mbo;} static struct comp_channel *get_channel(struct most_interface *iface, int id){	struct comp_channel *c, *tmp;	unsigned long flags; 	spin_lock_irqsave(&ch_list_lock, flags);	list_for_each_entry_safe(c, tmp, &channel_list, list) {		if ((c->iface == iface) && (c->channel_id == id)) {			spin_unlock_irqrestore(&ch_list_lock, flags);			return c;		}	}	spin_unlock_irqrestore(&ch_list_lock, flags);	return NULL;} static void stop_channel(struct comp_channel *c){	struct mbo *mbo; 	while (kfifo_out((struct kfifo *)&c->fifo, &mbo, 1))		most_put_mbo(mbo);	most_stop_channel(c->iface, c->channel_id, &comp.cc);} static void destroy_cdev(struct comp_channel *c){	unsigned long flags; 	device_destroy(&most_cdev_class, c->devno);	cdev_del(&c->cdev);	spin_lock_irqsave(&ch_list_lock, flags);	list_del(&c->list);	spin_unlock_irqrestore(&ch_list_lock, flags);} static void destroy_channel(struct comp_channel *c){	ida_free(&comp.minor_id, MINOR(c->devno));	kfifo_free(&c->fifo);	kfree(c);} /** * comp_open - implements the syscall to open the device * @inode: inode pointer * @filp: file pointer * * This stores the channel pointer in the private data field of * the file structure and activates the channel within the core. */static int comp_open(struct inode *inode, struct file *filp){	struct comp_channel *c;	int ret; 	c = to_channel(inode->i_cdev);	filp->private_data = c; 	if (((c->cfg->direction == MOST_CH_RX) &&	     ((filp->f_flags & O_ACCMODE) != O_RDONLY)) ||	     ((c->cfg->direction == MOST_CH_TX) &&		((filp->f_flags & O_ACCMODE) != O_WRONLY))) {		return -EACCES;	} 	mutex_lock(&c->io_mutex);	if (!c->dev) {		mutex_unlock(&c->io_mutex);		return -ENODEV;	} 	if (c->access_ref) {		mutex_unlock(&c->io_mutex);		return -EBUSY;	} 	c->mbo_offs = 0;	ret = most_start_channel(c->iface, c->channel_id, &comp.cc);	if (!ret)		c->access_ref = 1;	mutex_unlock(&c->io_mutex);	return ret;} /** * comp_close - implements the syscall to close the device * @inode: inode pointer * @filp: file pointer * * This stops the channel within the core. */static int comp_close(struct inode *inode, struct file *filp){	struct comp_channel *c = to_channel(inode->i_cdev); 	mutex_lock(&c->io_mutex);	spin_lock(&c->unlink);	c->access_ref = 0;	spin_unlock(&c->unlink);	if (c->dev) {		stop_channel(c);		mutex_unlock(&c->io_mutex);	} else {		mutex_unlock(&c->io_mutex);		destroy_channel(c);	}	return 0;} /** * comp_write - implements the syscall to write to the device * @filp: file pointer * @buf: pointer to user buffer * @count: number of bytes to write * @offset: offset from where to start writing */static ssize_t comp_write(struct file *filp, const char __user *buf,			  size_t count, loff_t *offset){	int ret;	size_t to_copy, left;	struct mbo *mbo = NULL;	struct comp_channel *c = filp->private_data; 	mutex_lock(&c->io_mutex);	while (c->dev && !ch_get_mbo(c, &mbo)) {		mutex_unlock(&c->io_mutex); 		if ((filp->f_flags & O_NONBLOCK))			return -EAGAIN;		if (wait_event_interruptible(c->wq, ch_has_mbo(c) || !c->dev))			return -ERESTARTSYS;		mutex_lock(&c->io_mutex);	} 	if (unlikely(!c->dev)) {		ret = -ENODEV;		goto unlock;	} 	to_copy = min(count, c->cfg->buffer_size - c->mbo_offs);	left = copy_from_user(mbo->virt_address + c->mbo_offs, buf, to_copy);	if (left == to_copy) {		ret = -EFAULT;		goto unlock;	} 	c->mbo_offs += to_copy - left;	if (c->mbo_offs >= c->cfg->buffer_size ||	    c->cfg->data_type == MOST_CH_CONTROL ||	    c->cfg->data_type == MOST_CH_ASYNC) {		kfifo_skip(&c->fifo);		mbo->buffer_length = c->mbo_offs;		c->mbo_offs = 0;		most_submit_mbo(mbo);	} 	ret = to_copy - left;unlock:	mutex_unlock(&c->io_mutex);	return ret;} /** * comp_read - implements the syscall to read from the device * @filp: file pointer * @buf: pointer to user buffer * @count: number of bytes to read * @offset: offset from where to start reading */static ssize_tcomp_read(struct file *filp, char __user *buf, size_t count, loff_t *offset){	size_t to_copy, not_copied, copied;	struct mbo *mbo = NULL;	struct comp_channel *c = filp->private_data; 	mutex_lock(&c->io_mutex);	while (c->dev && !kfifo_peek(&c->fifo, &mbo)) {		mutex_unlock(&c->io_mutex);		if (filp->f_flags & O_NONBLOCK)			return -EAGAIN;		if (wait_event_interruptible(c->wq,					     (!kfifo_is_empty(&c->fifo) ||					      (!c->dev))))			return -ERESTARTSYS;		mutex_lock(&c->io_mutex);	} 	/* make sure we don't submit to gone devices */	if (unlikely(!c->dev)) {		mutex_unlock(&c->io_mutex);		return -ENODEV;	} 	to_copy = min_t(size_t,			count,			mbo->processed_length - c->mbo_offs); 	not_copied = copy_to_user(buf,				  mbo->virt_address + c->mbo_offs,				  to_copy); 	copied = to_copy - not_copied; 	c->mbo_offs += copied;	if (c->mbo_offs >= mbo->processed_length) {		kfifo_skip(&c->fifo);		most_put_mbo(mbo);		c->mbo_offs = 0;	}	mutex_unlock(&c->io_mutex);	return copied;} static __poll_t comp_poll(struct file *filp, poll_table *wait){	struct comp_channel *c = filp->private_data;	__poll_t mask = 0; 	poll_wait(filp, &c->wq, wait); 	mutex_lock(&c->io_mutex);	if (c->cfg->direction == MOST_CH_RX) {		if (!c->dev || !kfifo_is_empty(&c->fifo))			mask |= EPOLLIN | EPOLLRDNORM;	} else {		if (!c->dev || !kfifo_is_empty(&c->fifo) || ch_has_mbo(c))			mask |= EPOLLOUT | EPOLLWRNORM;	}	mutex_unlock(&c->io_mutex);	return mask;} /* * Initialization of struct file_operations */static const struct file_operations channel_fops = {	.owner = THIS_MODULE,	.read = comp_read,	.write = comp_write,	.open = comp_open,	.release = comp_close,	.poll = comp_poll,}; /** * comp_disconnect_channel - disconnect a channel * @iface: pointer to interface instance * @channel_id: channel index * * This frees allocated memory and removes the cdev that represents this * channel in user space. */static int comp_disconnect_channel(struct most_interface *iface, int channel_id){	struct comp_channel *c; 	c = get_channel(iface, channel_id);	if (!c)		return -EINVAL; 	mutex_lock(&c->io_mutex);	spin_lock(&c->unlink);	c->dev = NULL;	spin_unlock(&c->unlink);	destroy_cdev(c);	if (c->access_ref) {		stop_channel(c);		wake_up_interruptible(&c->wq);		mutex_unlock(&c->io_mutex);	} else {		mutex_unlock(&c->io_mutex);		destroy_channel(c);	}	return 0;} /** * comp_rx_completion - completion handler for rx channels * @mbo: pointer to buffer object that has completed * * This searches for the channel linked to this MBO and stores it in the local * fifo buffer. */static int comp_rx_completion(struct mbo *mbo){	struct comp_channel *c; 	if (!mbo)		return -EINVAL; 	c = get_channel(mbo->ifp, mbo->hdm_channel_id);	if (!c)		return -EINVAL; 	spin_lock(&c->unlink);	if (!c->access_ref || !c->dev) {		spin_unlock(&c->unlink);		return -ENODEV;	}	kfifo_in(&c->fifo, &mbo, 1);	spin_unlock(&c->unlink);#ifdef DEBUG_MESG	if (kfifo_is_full(&c->fifo))		dev_warn(c->dev, "Fifo is full\n");#endif	wake_up_interruptible(&c->wq);	return 0;} /** * comp_tx_completion - completion handler for tx channels * @iface: pointer to interface instance * @channel_id: channel index/ID * * This wakes sleeping processes in the wait-queue. */static int comp_tx_completion(struct most_interface *iface, int channel_id){	struct comp_channel *c; 	c = get_channel(iface, channel_id);	if (!c)		return -EINVAL; 	if ((channel_id < 0) || (channel_id >= iface->num_channels)) {		dev_warn(c->dev, "Channel ID out of range\n");		return -EINVAL;	} 	wake_up_interruptible(&c->wq);	return 0;} /** * comp_probe - probe function of the driver module * @iface: pointer to interface instance * @channel_id: channel index/ID * @cfg: pointer to actual channel configuration * @name: name of the device to be created * @args: pointer to array of component parameters (from configfs) * * This allocates a channel object and creates the device node in /dev * * Returns 0 on success or error code otherwise. */static int comp_probe(struct most_interface *iface, int channel_id,		      struct most_channel_config *cfg, char *name, char *args){	struct comp_channel *c;	unsigned long cl_flags;	int retval;	int current_minor; 	if (!cfg || !name)		return -EINVAL; 	c = get_channel(iface, channel_id);	if (c)		return -EEXIST; 	current_minor = ida_alloc(&comp.minor_id, GFP_KERNEL);	if (current_minor < 0)		return current_minor; 	c = kzalloc_obj(*c);	if (!c) {		retval = -ENOMEM;		goto err_remove_ida;	} 	c->devno = MKDEV(comp.major, current_minor);	cdev_init(&c->cdev, &channel_fops);	c->cdev.owner = THIS_MODULE;	retval = cdev_add(&c->cdev, c->devno, 1);	if (retval < 0)		goto err_free_c;	c->iface = iface;	c->cfg = cfg;	c->channel_id = channel_id;	c->access_ref = 0;	spin_lock_init(&c->unlink);	INIT_KFIFO(c->fifo);	retval = kfifo_alloc(&c->fifo, cfg->num_buffers, GFP_KERNEL);	if (retval)		goto err_del_cdev_and_free_channel;	init_waitqueue_head(&c->wq);	mutex_init(&c->io_mutex);	spin_lock_irqsave(&ch_list_lock, cl_flags);	list_add_tail(&c->list, &channel_list);	spin_unlock_irqrestore(&ch_list_lock, cl_flags);	c->dev = device_create(&most_cdev_class, NULL, c->devno, NULL, "%s", name); 	if (IS_ERR(c->dev)) {		retval = PTR_ERR(c->dev);		goto err_free_kfifo_and_del_list;	}	kobject_uevent(&c->dev->kobj, KOBJ_ADD);	return 0; err_free_kfifo_and_del_list:	kfifo_free(&c->fifo);	list_del(&c->list);err_del_cdev_and_free_channel:	cdev_del(&c->cdev);err_free_c:	kfree(c);err_remove_ida:	ida_free(&comp.minor_id, current_minor);	return retval;} static struct cdev_component comp = {	.cc = {		.mod = THIS_MODULE,		.name = "cdev",		.probe_channel = comp_probe,		.disconnect_channel = comp_disconnect_channel,		.rx_completion = comp_rx_completion,		.tx_completion = comp_tx_completion,	},};  static int __init most_cdev_init(void){	int err; 	err = class_register(&most_cdev_class);	if (err)		return err;

Lines 1–500 of 546.