tegrakernel/kernel/nvidia/drivers/video/tegra/host/vi/vi_notify.c

720 lines
17 KiB
C

/*
* VI NOTIFY driver for T186
*
* Copyright (c) 2015-2019 NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <asm/ioctls.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kfifo.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
#include <linux/sched/signal.h>
#endif
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <media/vi.h>
#include "vi_notify.h"
/* XXX: move ioctls to include/linux/ (after T18X merge) */
#include <uapi/linux/nvhost_vi_ioctl.h>
#define NVHOST_VI_SET_IGN_MASK _IOW(NVHOST_VI_IOCTL_MAGIC, 10, u32)
#define NVHOST_VI_SET_SYNCPTS \
_IOW(NVHOST_VI_IOCTL_MAGIC, 13, struct tegra_vi4_syncpts_req)
#define NVHOST_VI_ENABLE_REPORTS \
_IOW(NVHOST_VI_IOCTL_MAGIC, 14, struct tegra_vi4_syncpts_req)
#define NVHOST_VI_RESET_CHANNEL \
_IOW(NVHOST_VI_IOCTL_MAGIC, 15, struct tegra_vi4_syncpts_req)
#define NVHOST_VI_GET_CAPTURE_STATUS \
_IOWR(NVHOST_VI_IOCTL_MAGIC, 16, struct vi_capture_status)
struct vi_notify_dev {
struct vi_notify_driver *driver;
struct device *device;
dev_t major;
u8 num_channels;
struct mutex lock;
struct tegra_vi_mfi_ctx *mfi_ctx;
struct vi_notify_channel __rcu *channels[];
};
#define VI_NOTIFY_TAG_CHANSEL_NLINES 0x08
static int vi_notify_dev_classify(struct vi_notify_dev *vnd)
{
struct vi_notify_channel *chan;
u32 ign_mask = -1;
unsigned i;
bool active = false;
for (i = 0; i < vnd->num_channels; i++) {
chan = rcu_access_pointer(vnd->channels[i]);
if (chan != NULL) {
ign_mask &= atomic_read(&chan->ign_mask);
active = true;
}
}
/* Unmask NLINES event, for Mid-Frame Interrupt */
if (active && tegra_vi_has_mfi_callback())
ign_mask &= ~(1u << VI_NOTIFY_TAG_CHANSEL_NLINES);
return vnd->driver->classify(vnd->device, ~ign_mask);
}
static unsigned vi_notify_occupancy(struct vi_notify_channel *chan)
{
unsigned ret;
mutex_lock(&chan->read_lock);
ret = kfifo_len(&chan->fifo);
mutex_unlock(&chan->read_lock);
return ret;
}
void vi_notify_channel_set_notify_funcs(struct vi_notify_channel *chan,
vi_notify_status_callback notify,
vi_notify_error_callback error,
void *client_data)
{
chan->notify_cb = notify;
chan->error_cb = error;
chan->client_data = client_data;
}
EXPORT_SYMBOL(vi_notify_channel_set_notify_funcs);
/* Interrupt handlers */
void vi_notify_dev_error(struct vi_notify_dev *vnd)
{
struct vi_notify_channel *chan;
unsigned i;
rcu_read_lock();
for (i = 0; i < vnd->num_channels; i++) {
chan = rcu_dereference(vnd->channels[i]);
if (chan != NULL) {
atomic_set(&chan->errors, 1);
if (chan->error_cb)
chan->error_cb(chan->client_data);
else
wake_up(&chan->readq);
}
}
rcu_read_unlock();
}
EXPORT_SYMBOL(vi_notify_dev_error);
void vi_notify_dev_report(struct vi_notify_dev *vnd, u8 channel,
const struct vi_capture_status *status)
{
struct vi_notify_channel *chan;
rcu_read_lock();
chan = rcu_dereference(vnd->channels[channel]);
if (chan != NULL && !atomic_read(&chan->report)) {
chan->status = *status;
atomic_set(&chan->report, 1);
if (chan->notify_cb)
chan->notify_cb(chan, status, chan->client_data);
else
wake_up(&chan->readq);
}
rcu_read_unlock();
}
EXPORT_SYMBOL(vi_notify_dev_report);
static bool vi_notify_is_broadcast(u8 tag)
{
return (1u << tag) & (
(1u << 0) /* CSI mux frame start */ |
(1u << 1) /* CSI mux frame end */ |
(1u << 2) /* CSI mux frame fault */ |
(1u << 3) /* CSI mux stream fault */ |
(1u << 10) /* Frame end fault */ |
(1u << 11) /* No match fault */ |
(1u << 12) /* Match collision fault */ |
(1u << 13) /* Short frame fault */ |
(1u << 14) /* Load collision */);
}
static void vi_notify_recv(struct vi_notify_dev *vnd,
const struct vi_notify_msg *msg, u8 channel)
{
struct vi_notify_channel *chan;
u8 tag = VI_NOTIFY_TAG_TAG(msg->tag);
rcu_read_lock();
chan = rcu_dereference(vnd->channels[channel]);
/* Notify NLINES event to tegra_vi */
if (tag == VI_NOTIFY_TAG_CHANSEL_NLINES)
tegra_vi_mfi_event_notify(vnd->mfi_ctx, channel);
if (chan != NULL && !((1u << tag) & atomic_read(&chan->ign_mask))) {
if (!kfifo_put(&chan->fifo, *msg))
atomic_set(&chan->overruns, 1);
wake_up(&chan->readq);
}
rcu_read_unlock();
}
void vi_notify_dev_recv(struct vi_notify_dev *vnd,
const struct vi_notify_msg *msg)
{
u8 channel = VI_NOTIFY_TAG_CHANNEL(msg->tag);
u8 tag = VI_NOTIFY_TAG_TAG(msg->tag);
dev_dbg(vnd->device, "Message: tag:%2u channel:%02X frame:%04X\n",
tag, channel, VI_NOTIFY_TAG_FRAME(msg->tag));
dev_dbg(vnd->device, " timestamp %u data 0x%08x",
msg->stamp, msg->data);
if (vi_notify_is_broadcast(tag)) {
for (channel = 0; channel < vnd->num_channels; channel++)
vi_notify_recv(vnd, msg, channel);
} else if (channel >= vnd->num_channels)
dev_warn(vnd->device, "Channel %u out of range!\n", channel);
else
vi_notify_recv(vnd, msg, channel);
}
EXPORT_SYMBOL(vi_notify_dev_recv);
/* File operations */
static ssize_t vi_notify_read(struct file *file, char __user *buf, size_t len,
loff_t *offset)
{
struct vi_notify_channel *chan = file->private_data;
for (;;) {
DEFINE_WAIT(wait);
unsigned int copied;
int ret = 0;
unsigned report = atomic_read(&chan->report);
if (len < (report
? sizeof(struct vi_capture_status)
: sizeof(struct vi_notify_msg)))
return 0;
if (mutex_lock_interruptible(&chan->read_lock))
return -ERESTARTSYS;
if (report) {
copied = sizeof(chan->status);
if (copy_to_user(buf, &chan->status, copied))
ret = -EFAULT;
atomic_set(&chan->report, 0);
} else {
ret = kfifo_to_user(&chan->fifo, buf, len, &copied);
}
mutex_unlock(&chan->read_lock);
if (ret)
return ret;
if (copied > 0)
return copied;
prepare_to_wait(&chan->readq, &wait, TASK_INTERRUPTIBLE);
if (atomic_xchg(&chan->overruns, 0))
ret = -EOVERFLOW;
else if (atomic_xchg(&chan->errors, 0))
ret = -EIO;
else if (signal_pending(current))
ret = -ERESTARTSYS;
else if (file->f_flags & O_NONBLOCK)
ret = -EAGAIN;
else if (!vi_notify_occupancy(chan))
schedule();
finish_wait(&chan->readq, &wait);
if (ret)
return ret;
}
}
static unsigned int vi_notify_poll(struct file *file,
struct poll_table_struct *table)
{
struct vi_notify_channel *chan = file->private_data;
unsigned ret = 0;
poll_wait(file, &chan->readq, table);
if (vi_notify_occupancy(chan))
ret |= POLLIN | POLLRDNORM;
if (atomic_read(&chan->overruns) || atomic_read(&chan->errors))
ret |= POLLERR;
if (atomic_read(&chan->report))
ret |= POLLPRI;
return ret;
}
int vi_notify_channel_set_ign_mask(struct vi_notify_channel *chan, u32 mask)
{
struct vi_notify_dev *vnd = chan->vnd;
u32 old_mask;
int err;
if (mutex_lock_interruptible(&vnd->lock))
return -ERESTARTSYS;
old_mask = atomic_xchg(&chan->ign_mask, mask);
err = vi_notify_dev_classify(vnd);
if (err)
atomic_set(&chan->ign_mask, old_mask);
mutex_unlock(&vnd->lock);
return err;
}
EXPORT_SYMBOL(vi_notify_channel_set_ign_mask);
int vi_notify_channel_set_syncpts(unsigned channel,
struct vi_notify_channel *chan,
struct tegra_vi4_syncpts_req *req)
{
struct vi_notify_dev *vnd = chan->vnd;
int err;
if (!vnd->driver->set_syncpts)
return -ENOTSUPP;
if (req->pad)
return -EINVAL;
if (mutex_lock_interruptible(&vnd->lock))
return -ERESTARTSYS;
err = vnd->driver->set_syncpts(vnd->device, channel,
req->syncpt_ids);
mutex_unlock(&vnd->lock);
return err;
}
EXPORT_SYMBOL(vi_notify_channel_set_syncpts);
int vi_notify_channel_enable_reports(unsigned channel,
struct vi_notify_channel *chan,
struct tegra_vi4_syncpts_req *req)
{
struct vi_notify_dev *vnd = chan->vnd;
int err;
if (!vnd->driver->enable_reports)
return -ENOTSUPP;
if (req->pad)
return -EINVAL;
if (mutex_lock_interruptible(&vnd->lock))
return -ERESTARTSYS;
err = vnd->driver->enable_reports(vnd->device, channel,
req->stream, req->vc, req->syncpt_ids);
mutex_unlock(&vnd->lock);
dev_dbg(vnd->device, "vi_notify_channel_enable_reports: ch:%d",
channel);
return err;
}
EXPORT_SYMBOL(vi_notify_channel_enable_reports);
int vi_notify_channel_reset(unsigned channel,
struct vi_notify_channel *chan,
struct tegra_vi4_syncpts_req *req)
{
struct vi_notify_dev *vnd = chan->vnd;
if (!vnd->driver->reset_channel)
return -ENOTSUPP;
if (req->pad)
return -EINVAL;
if (mutex_lock_interruptible(&vnd->lock))
return -ERESTARTSYS;
vnd->driver->reset_channel(vnd->device, channel);
mutex_unlock(&vnd->lock);
return 0;
}
EXPORT_SYMBOL(vi_notify_channel_reset);
int vi_notify_get_capture_status(struct vi_notify_channel *chan,
unsigned channel,
u64 index,
struct vi_capture_status *status)
{
struct vi_notify_dev *vnd = chan->vnd;
int err = 0;
if (!vnd->driver->get_capture_status)
return -ENOTSUPP;
if (mutex_lock_interruptible(&vnd->lock))
return -ERESTARTSYS;
err = vnd->driver->get_capture_status(vnd->device,
channel, index, status);
mutex_unlock(&vnd->lock);
dev_dbg(vnd->device, "vi_notify_get_capture_status: ch:%2d sof_ts:%llu eof_ts:%llu idx:%llu\n",
channel, status->sof_ts, status->eof_ts, index);
return err;
}
EXPORT_SYMBOL(vi_notify_get_capture_status);
static long vi_notify_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct vi_notify_channel *chan = file->private_data;
unsigned channel = iminor(file_inode(file));
switch (cmd) {
case FIONREAD: {
int val;
val = vi_notify_occupancy(chan);
val *= sizeof(struct vi_notify_msg);
return put_user(val, (int __user *)arg);
}
case NVHOST_VI_SET_IGN_MASK: {
u32 mask;
if (get_user(mask, (u32 __user *)arg))
return -EFAULT;
return vi_notify_channel_set_ign_mask(chan, mask);
}
case NVHOST_VI_SET_SYNCPTS: {
struct tegra_vi4_syncpts_req req;
if (copy_from_user(&req, (void __user *)arg, sizeof(req)))
return -EFAULT;
return vi_notify_channel_set_syncpts(channel, chan, &req);
}
case NVHOST_VI_ENABLE_REPORTS: {
struct tegra_vi4_syncpts_req req;
if (copy_from_user(&req, (void __user *)arg, sizeof(req)))
return -EFAULT;
return vi_notify_channel_enable_reports(channel, chan, &req);
}
case NVHOST_VI_RESET_CHANNEL: {
struct tegra_vi4_syncpts_req req;
if (copy_from_user(&req, (void __user *)arg, sizeof(req)))
return -EFAULT;
return vi_notify_channel_reset(channel, chan, &req);
}
case NVHOST_VI_GET_CAPTURE_STATUS: {
int err = 0;
u64 index = 0;
struct vi_capture_status status;
if (copy_from_user(&index, (void __user *)arg, sizeof(u64)))
return -EFAULT;
err = vi_notify_get_capture_status(chan, channel,
index, &status);
if (likely(err == 0)) {
if (copy_to_user((void __user *)arg, &status,
sizeof(status)))
return -EFAULT;
}
return err;
}
}
return -ENOIOCTLCMD;
}
static struct vi_notify_dev *vnd_;
static DEFINE_MUTEX(vnd_lock);
static int vi_notifier_busy(struct vi_notify_dev *vnd)
{
if (vnd->driver->runtime_get) {
int err = vnd->driver->runtime_get(vnd->device);
if (err < 0) {
dev_err(vnd->device, "runtime_get() failed: %d\n", err);
return err;
}
}
return 0;
}
static inline void vi_notifier_idle(struct vi_notify_dev *vnd)
{
if (vnd->driver->runtime_put)
vnd->driver->runtime_put(vnd->device);
}
struct vi_notify_channel *vi_notify_channel_open(unsigned channel)
{
struct vi_notify_dev *vnd;
struct vi_notify_channel *chan;
int err;
if (mutex_lock_interruptible(&vnd_lock))
return ERR_PTR(-ERESTARTSYS);
vnd = vnd_;
if (vnd == NULL || channel >= vnd->num_channels ||
!try_module_get(vnd->driver->owner)) {
mutex_unlock(&vnd_lock);
return ERR_PTR(-ENODEV);
}
mutex_unlock(&vnd_lock);
err = vi_notifier_busy(vnd);
if (err < 0)
goto module_put;
if (vnd->driver->has_notifier_backend) {
if (!vnd->driver->has_notifier_backend(vnd->device)) {
/* TODO: poll/wait and recheck before returning error */
err = -ENODEV;
goto runtime_put;
}
}
chan = kzalloc(sizeof(*chan), GFP_KERNEL);
if (unlikely(chan == NULL)) {
err = -ENOMEM;
goto runtime_put;
}
chan->vnd = vnd;
atomic_set(&chan->ign_mask, 0xffffffff);
init_waitqueue_head(&chan->readq);
mutex_init(&chan->read_lock);
atomic_set(&chan->overruns, 0);
atomic_set(&chan->errors, 0);
atomic_set(&chan->report, 0);
INIT_KFIFO(chan->fifo);
mutex_lock(&vnd->lock);
if (rcu_access_pointer(vnd->channels[channel]) != NULL) {
mutex_unlock(&vnd->lock);
kfree(chan);
err = -EBUSY;
goto runtime_put;
}
rcu_assign_pointer(vnd->channels[channel], chan);
vi_notify_dev_classify(vnd);
mutex_unlock(&vnd->lock);
return chan;
runtime_put:
vi_notifier_idle(vnd);
module_put:
module_put(vnd->driver->owner);
return ERR_PTR(err);
}
EXPORT_SYMBOL(vi_notify_channel_open);
int vi_notify_channel_close(unsigned channel, struct vi_notify_channel *chan)
{
struct vi_notify_dev *vnd = chan->vnd;
mutex_lock(&vnd->lock);
if (vnd->driver->reset_channel)
vnd->driver->reset_channel(vnd->device, channel);
WARN_ON(rcu_access_pointer(vnd->channels[channel]) != chan);
RCU_INIT_POINTER(vnd->channels[channel], NULL);
vi_notify_dev_classify(vnd);
mutex_unlock(&vnd->lock);
kfree_rcu(chan, rcu);
vi_notifier_idle(vnd);
module_put(vnd->driver->owner);
return 0;
}
EXPORT_SYMBOL(vi_notify_channel_close);
static int vi_notify_open(struct inode *inode, struct file *file)
{
struct vi_notify_channel *chan;
unsigned channel = iminor(inode);
if ((file->f_flags & O_ACCMODE) != O_RDONLY)
return -EINVAL;
chan = vi_notify_channel_open(channel);
if (IS_ERR(chan))
return PTR_ERR(chan);
file->private_data = chan;
return nonseekable_open(inode, file);
}
static int vi_notify_release(struct inode *inode, struct file *file)
{
struct vi_notify_channel *chan = file->private_data;
unsigned channel = iminor(inode);
return vi_notify_channel_close(channel, chan);
}
static const struct file_operations vi_notify_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = vi_notify_read,
.poll = vi_notify_poll,
.unlocked_ioctl = vi_notify_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vi_notify_ioctl,
#endif
.open = vi_notify_open,
.release = vi_notify_release,
};
/* Character device */
static struct class *vi_notify_class;
static int vi_notify_major;
int vi_notify_register(struct vi_notify_driver *drv, struct device *dev,
u8 num_channels)
{
struct vi_notify_dev *vnd;
int err;
unsigned i;
vnd = devm_kzalloc(dev, sizeof(*vnd) + num_channels *
sizeof(struct vi_notify_channel *), GFP_KERNEL);
if (unlikely(vnd == NULL))
return -ENOMEM;
vnd->driver = drv;
vnd->device = dev;
vnd->num_channels = num_channels;
mutex_init(&vnd->lock);
err = vnd->driver->probe(vnd->device, vnd);
if (err)
return err;
err = tegra_vi_init_mfi(&vnd->mfi_ctx, num_channels);
if (err)
goto error;
mutex_lock(&vnd_lock);
if (vnd_ != NULL) {
mutex_unlock(&vnd_lock);
tegra_vi_deinit_mfi(&vnd->mfi_ctx);
WARN_ON(1);
err = -EBUSY;
goto error;
}
vnd_ = vnd;
mutex_unlock(&vnd_lock);
for (i = 0; i < vnd->num_channels; i++) {
dev_t devt = MKDEV(vi_notify_major, i);
device_create(vi_notify_class, vnd->device, devt, NULL,
"tegra-vi0-channel%u", i);
}
return 0;
error:
if (vnd->driver->remove)
vnd->driver->remove(dev);
return err;
}
EXPORT_SYMBOL(vi_notify_register);
void vi_notify_unregister(struct vi_notify_driver *drv, struct device *dev)
{
struct vi_notify_dev *vnd;
unsigned i;
mutex_lock(&vnd_lock);
vnd = vnd_;
vnd_ = NULL;
WARN_ON(vnd->driver != drv);
WARN_ON(vnd->device != dev);
mutex_unlock(&vnd_lock);
tegra_vi_deinit_mfi(&vnd->mfi_ctx);
for (i = 0; i < vnd->num_channels; i++) {
dev_t devt = MKDEV(vi_notify_major, i);
device_destroy(vi_notify_class, devt);
}
if (vnd->driver->remove)
vnd->driver->remove(vnd->device);
devm_kfree(vnd->device, vnd);
}
EXPORT_SYMBOL(vi_notify_unregister);
static int __init vi_notify_init(void)
{
vi_notify_class = class_create(THIS_MODULE, "tegra-vi-channel");
if (IS_ERR(vi_notify_class))
return PTR_ERR(vi_notify_class);
vi_notify_major = register_chrdev(0, "tegra-vi-channel",
&vi_notify_fops);
if (vi_notify_major < 0) {
class_destroy(vi_notify_class);
return vi_notify_major;
}
return 0;
}
static void __exit vi_notify_exit(void)
{
unregister_chrdev(vi_notify_major, "tegra-vi-channel");
class_destroy(vi_notify_class);
}
subsys_initcall(vi_notify_init);
module_exit(vi_notify_exit);