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