365 lines
7.4 KiB
C
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;
|
|
}
|