365 lines
7.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2024 Rockchip Electronics Co., Ltd.
* Author: Jason Zhang <jason.zhang@rock-chips.com>
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/poll.h>
#include "it6621.h"
#include "it6621-earc.h"
#include "it6621-uapi.h"
#define IT6621_MAX_MSGS 18
#define IT6621_MAX_MSG_LEN 256
struct it6621_msg {
u8 event;
u8 data[IT6621_MAX_MSG_LEN];
} __packed;
/* Command */
#define IT6621_EARC_SET_ENABLED _IOW('a', 0x01, unsigned int)
#define IT6621_EARC_GET_STATE _IOR('a', 0x02, unsigned int)
#define IT6621_EARC_GET_AUDIO_CAP _IOR('a', 0x03, u8[IT6621_MAX_MSG_LEN])
#define IT6621_EARC_GET_EVENT _IOR('a', 0x04, struct it6621_msg)
#define IT6621_EARC_SET_ENTER_ARC _IOW('a', 0x05, unsigned int)
struct it6621_msg_entry {
struct list_head list;
struct it6621_msg msg;
};
struct it6621_fh {
struct list_head list;
struct list_head msgs;
unsigned int msgs_num;
struct mutex msgs_lock;
wait_queue_head_t wait;
struct it6621_priv *priv;
struct mutex valid_lock;
bool valid;
};
static int minor;
static bool it6621_fh_get_valid(struct it6621_fh *fh)
{
bool valid;
mutex_lock(&fh->valid_lock);
valid = fh->valid;
mutex_unlock(&fh->valid_lock);
return valid;
}
static void it6621_fh_set_valid(struct it6621_fh *fh, bool valid)
{
mutex_lock(&fh->valid_lock);
fh->valid = valid;
mutex_unlock(&fh->valid_lock);
}
static int it6621_fh_set_earc_enabled(struct it6621_fh *fh,
unsigned int __user *argp)
{
struct it6621_priv *priv = fh->priv;
unsigned int enabled;
int ret;
ret = copy_from_user(&enabled, argp, sizeof(enabled));
if (ret)
return -EFAULT;
return it6621_set_earc_enabled(priv, !!enabled);
}
static int it6621_fh_get_earc_state(struct it6621_fh *fh,
unsigned int __user *argp)
{
struct it6621_priv *priv = fh->priv;
int ret;
ret = copy_to_user(argp, &priv->state, sizeof(priv->state));
if (ret)
return -EFAULT;
return 0;
}
static int it6621_fh_get_earc_audio_cap(struct it6621_fh *fh, u8 __user *argp)
{
struct it6621_priv *priv = fh->priv;
int ret;
mutex_lock(&priv->rxcap_lock);
ret = copy_to_user(argp, priv->rxcap, sizeof(priv->rxcap));
mutex_unlock(&priv->rxcap_lock);
if (ret)
return -EFAULT;
return 0;
}
static int it6621_fh_get_msg(struct it6621_fh *fh,
struct it6621_msg __user *argp,
bool block)
{
struct it6621_msg_entry *entry;
int ret = 0;
do {
mutex_lock(&fh->msgs_lock);
if (fh->msgs_num) {
entry = list_first_entry(&fh->msgs,
struct it6621_msg_entry,
list);
list_del(&entry->list);
ret = copy_to_user(argp, &entry->msg, sizeof(entry->msg));
kfree(entry);
fh->msgs_num--;
mutex_unlock(&fh->msgs_lock);
if (ret)
return -EFAULT;
return 0;
}
mutex_unlock(&fh->msgs_lock);
if (!block)
return -EAGAIN;
ret = wait_event_interruptible(fh->wait, fh->msgs_num);
if (!it6621_fh_get_valid(fh))
return -ENXIO;
/* Exit on error, otherwise loop to get the new message */
} while (!ret);
return ret;
}
static int it6621_fh_push_msg(struct it6621_fh *fh, u8 event, void *data,
size_t len)
{
struct it6621_msg_entry *entry;
mutex_lock(&fh->msgs_lock);
if (fh->msgs_num <= IT6621_MAX_MSGS) {
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) {
mutex_unlock(&fh->msgs_lock);
return -ENOMEM;
}
entry->msg.event = event;
memcpy(entry->msg.data, data, len);
list_add_tail(&entry->list, &fh->msgs);
fh->msgs_num++;
} else {
dev_warn(fh->priv->dev, "queue is full for fh: %p", fh);
}
mutex_unlock(&fh->msgs_lock);
wake_up_interruptible(&fh->wait);
return 0;
}
static int it6621_fh_set_enter_arc(struct it6621_fh *fh,
unsigned int __user *argp)
{
struct it6621_priv *priv = fh->priv;
int enabled;
int ret;
ret = copy_from_user(&enabled, argp, sizeof(enabled));
if (ret)
return -EFAULT;
return it6621_set_enter_arc(priv, !!enabled);
}
static int it6621_uapi_open(struct inode *inode, struct file *file)
{
struct miscdevice *mdev = file->private_data;
struct it6621_priv *priv = container_of(mdev, struct it6621_priv, mdev);
struct it6621_fh *fh;
if (!priv->uapi_registered)
return -ENXIO;
fh = kzalloc(sizeof(*fh), GFP_KERNEL);
if (!fh)
return -ENOMEM;
fh->priv = priv;
fh->valid = true;
fh->msgs_num = 0;
INIT_LIST_HEAD(&fh->msgs);
mutex_init(&fh->msgs_lock);
mutex_init(&fh->valid_lock);
init_waitqueue_head(&fh->wait);
mutex_lock(&priv->fhs_lock);
list_add(&fh->list, &priv->fhs);
mutex_unlock(&priv->fhs_lock);
file->private_data = fh;
return 0;
}
static long it6621_uapi_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct it6621_fh *fh = file->private_data;
bool block = !(file->f_flags & O_NONBLOCK);
void __user *argp = (void __user *)arg;
if (!it6621_fh_get_valid(fh))
return -ENODEV;
switch (cmd) {
case IT6621_EARC_SET_ENABLED:
return it6621_fh_set_earc_enabled(fh, argp);
case IT6621_EARC_GET_STATE:
return it6621_fh_get_earc_state(fh, argp);
case IT6621_EARC_GET_AUDIO_CAP:
return it6621_fh_get_earc_audio_cap(fh, argp);
case IT6621_EARC_GET_EVENT:
return it6621_fh_get_msg(fh, argp, block);
case IT6621_EARC_SET_ENTER_ARC:
return it6621_fh_set_enter_arc(fh, argp);
default:
return -ENOTTY;
}
}
static int it6621_uapi_release(struct inode *inode, struct file *file)
{
struct it6621_fh *fh = file->private_data;
struct it6621_priv *priv = fh->priv;
struct it6621_msg_entry *entry;
if (it6621_fh_get_valid(fh)) {
mutex_lock(&priv->fhs_lock);
list_del(&fh->list);
mutex_unlock(&priv->fhs_lock);
}
mutex_lock(&fh->msgs_lock);
while (!list_empty(&fh->msgs)) {
entry = list_first_entry(&fh->msgs, struct it6621_msg_entry,
list);
list_del(&entry->list);
kfree(entry);
}
mutex_unlock(&fh->msgs_lock);
kfree(fh);
file->private_data = NULL;
return 0;
}
static __poll_t it6621_uapi_poll(struct file *file,
struct poll_table_struct *poll)
{
struct it6621_fh *fh = file->private_data;
__poll_t ret = 0;
poll_wait(file, &fh->wait, poll);
if (!it6621_fh_get_valid(fh))
ret = EPOLLERR | EPOLLHUP;
else
ret = EPOLLIN | EPOLLRDNORM;
return ret;
}
const struct file_operations it6621_uapi_fops = {
.owner = THIS_MODULE,
.open = it6621_uapi_open,
.unlocked_ioctl = it6621_uapi_ioctl,
.compat_ioctl = it6621_uapi_ioctl,
.release = it6621_uapi_release,
.poll = it6621_uapi_poll,
.llseek = no_llseek,
};
int it6621_uapi_init(struct it6621_priv *priv)
{
char *name;
int ret;
name = kzalloc(NAME_MAX, GFP_KERNEL);
if (!name)
return -ENOMEM;
snprintf(name, NAME_MAX, "ite-earc%d", minor++);
priv->mdev.minor = MISC_DYNAMIC_MINOR;
priv->mdev.name = name;
priv->mdev.fops = &it6621_uapi_fops;
ret = misc_register(&priv->mdev);
if (ret)
return ret;
priv->uapi_registered = true;
return 0;
}
void it6621_uapi_remove(struct it6621_priv *priv)
{
struct it6621_fh *fh;
mutex_lock(&priv->fhs_lock);
priv->uapi_registered = false;
list_for_each_entry(fh, &priv->fhs, list)
it6621_fh_set_valid(fh, false);
list_for_each_entry(fh, &priv->fhs, list)
wake_up_interruptible(&fh->wait);
mutex_unlock(&priv->fhs_lock);
misc_deregister(&priv->mdev);
kfree(priv->mdev.name);
}
int it6621_uapi_msg(struct it6621_priv *priv, u8 event, void *data, size_t len)
{
struct it6621_fh *fh;
int ret = 0;
if (!priv->uapi_registered)
return 0;
mutex_lock(&priv->fhs_lock);
list_for_each_entry(fh, &priv->fhs, list) {
ret = it6621_fh_push_msg(fh, event, data, len);
if (ret)
break;
}
mutex_unlock(&priv->fhs_lock);
return ret;
}