mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
0afabe230c
PD#SWPL-88882 Problem: kernel through to user space rw files Solution: add debug file function Verify: sc2 Signed-off-by: qinglin.li <qinglin.li@amlogic.com> Change-Id: I760ac112d21b2ba9d0a86f725771455627d8154f
301 lines
7.0 KiB
C
301 lines
7.0 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/kfifo.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/amlogic/debug_file.h>
|
|
|
|
#define FILE_CLOSE_FROM_KERNEL 0x00
|
|
#define FILE_OPEN_FROM_KERNEL 0x01
|
|
#define FILE_CLOSE_READY 0x02
|
|
|
|
#define FILE_READ_END 0x04
|
|
#define FILE_READ_ERR 0x08
|
|
#define FILE_READ_TO_FIFO_ERR 0x10
|
|
|
|
#define FILE_WRITE_GET_FIFO_ERR 0x20
|
|
#define FILE_WRITE_ERR 0x40
|
|
|
|
static struct kfifo files_info;
|
|
static struct proc_dir_entry *proc_debug_file;
|
|
static struct proc_dir_entry *proc_files_info;
|
|
static LIST_HEAD(files_list);
|
|
static LIST_HEAD(files_release_list);
|
|
static DEFINE_MUTEX(files_info_lock);
|
|
static DEFINE_MUTEX(file_debug_lock);
|
|
|
|
static atomic_t file_id = ATOMIC_INIT(0);
|
|
|
|
static ssize_t files_info_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int ret;
|
|
unsigned int copied;
|
|
|
|
mutex_lock(&files_info_lock);
|
|
ret = kfifo_to_user(&files_info, buf, count, &copied);
|
|
mutex_unlock(&files_info_lock);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return copied;
|
|
}
|
|
|
|
static ssize_t files_info_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct debug_file *filp;
|
|
struct debug_file_param param;
|
|
|
|
if (copy_from_user(¶m, buf, sizeof(struct debug_file_param)))
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&file_debug_lock);
|
|
list_for_each_entry(filp, &files_list, list) {
|
|
if (filp->param.id == param.id) {
|
|
filp->param.status = param.status;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&file_debug_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct proc_ops files_info_proc_ops = {
|
|
.proc_read = files_info_read,
|
|
.proc_write = files_info_write,
|
|
.proc_lseek = noop_llseek,
|
|
};
|
|
|
|
static ssize_t file_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int ret;
|
|
unsigned int copied;
|
|
struct debug_file *filp = PDE_DATA(file_inode(file));
|
|
|
|
ret = kfifo_to_user(&filp->kfifo_buf, buf, count, &copied);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return copied;
|
|
}
|
|
|
|
static ssize_t file_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int ret;
|
|
unsigned int copied;
|
|
struct debug_file *filp = PDE_DATA(file_inode(file));
|
|
|
|
ret = kfifo_from_user(&filp->kfifo_buf, buf, count, &copied);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
return copied;
|
|
}
|
|
|
|
static int file_close(struct inode *inode, struct file *file)
|
|
{
|
|
struct debug_file *filp = PDE_DATA(inode);
|
|
|
|
mutex_lock(&files_info_lock);
|
|
kfifo_in(&files_info, &filp->param, sizeof(struct debug_file_param));
|
|
mutex_unlock(&files_info_lock);
|
|
|
|
mutex_lock(&file_debug_lock);
|
|
if (filp->param.status == FILE_CLOSE_READY) {
|
|
list_del(&filp->list);
|
|
list_add(&filp->list, &files_release_list);
|
|
schedule_delayed_work(&filp->release_work, msecs_to_jiffies(100));
|
|
}
|
|
mutex_unlock(&file_debug_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct proc_ops file_proc_ops = {
|
|
.proc_read = file_read,
|
|
.proc_write = file_write,
|
|
.proc_lseek = noop_llseek,
|
|
.proc_release = file_close,
|
|
};
|
|
|
|
static void release_debug_file(struct debug_file *filp)
|
|
{
|
|
mutex_lock(&file_debug_lock);
|
|
list_del(&filp->list);
|
|
proc_remove(filp->proc_file);
|
|
kfifo_free(&filp->kfifo_buf);
|
|
kfree(filp);
|
|
mutex_unlock(&file_debug_lock);
|
|
}
|
|
|
|
static void release_work(struct work_struct *work)
|
|
{
|
|
struct debug_file *filp;
|
|
|
|
filp = container_of(work, struct debug_file, release_work.work);
|
|
release_debug_file(filp);
|
|
}
|
|
|
|
struct debug_file *debug_file_open(const char *filename, int flags, umode_t mode)
|
|
{
|
|
int ret;
|
|
struct debug_file *filp;
|
|
|
|
if (flags & O_RDWR)
|
|
return NULL;
|
|
|
|
if (kfifo_avail(&files_info) < sizeof(struct debug_file_param) * 10)
|
|
return NULL;
|
|
|
|
filp = kzalloc(sizeof(*filp), GFP_KERNEL);
|
|
if (!filp)
|
|
return NULL;
|
|
|
|
strncpy(filp->param.filename, filename, DEBUG_FILE_NAME_LEN - 1);
|
|
filp->param.flags = flags;
|
|
filp->param.mode = mode;
|
|
filp->param.status = FILE_OPEN_FROM_KERNEL;
|
|
|
|
INIT_DELAYED_WORK(&filp->release_work, release_work);
|
|
mutex_init(&filp->mutex);
|
|
ret = kfifo_alloc(&filp->kfifo_buf, (2 * PAGE_SIZE), GFP_KERNEL);
|
|
if (ret) {
|
|
pr_err("error kfifo_alloc for %s\n", filename);
|
|
goto err1;
|
|
}
|
|
filp->proc_file = proc_create_data(kbasename(filp->param.filename),
|
|
0664,
|
|
proc_debug_file,
|
|
&file_proc_ops,
|
|
(void *)filp);
|
|
if (!filp->proc_file) {
|
|
pr_err("failed to create files_info proc file\n");
|
|
goto err2;
|
|
}
|
|
|
|
mutex_lock(&file_debug_lock);
|
|
filp->param.id = atomic_read(&file_id);
|
|
atomic_inc(&file_id);
|
|
list_add(&filp->list, &files_list);
|
|
mutex_unlock(&file_debug_lock);
|
|
|
|
pr_info("%s-%d id=%d\n", __func__, __LINE__, filp->param.id);
|
|
mutex_lock(&files_info_lock);
|
|
kfifo_in(&files_info, &filp->param, sizeof(struct debug_file_param));
|
|
mutex_unlock(&files_info_lock);
|
|
|
|
return filp;
|
|
|
|
err2:
|
|
kfifo_free(&filp->kfifo_buf);
|
|
err1:
|
|
kfree(filp);
|
|
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(debug_file_open);
|
|
|
|
ssize_t debug_file_read(struct debug_file *filp, void *buf, size_t count)
|
|
{
|
|
int ret = kfifo_out(&filp->kfifo_buf, buf, count);
|
|
|
|
if (!ret && filp->param.status != FILE_OPEN_FROM_KERNEL)
|
|
return filp->param.status * -1;
|
|
|
|
return ret; // return read bytes
|
|
}
|
|
EXPORT_SYMBOL(debug_file_read);
|
|
|
|
ssize_t debug_file_write(struct debug_file *filp, const void *buf, size_t count)
|
|
{
|
|
if (filp->param.status != FILE_OPEN_FROM_KERNEL)
|
|
return filp->param.status * -1;
|
|
|
|
return kfifo_in(&filp->kfifo_buf, buf, count);
|
|
}
|
|
EXPORT_SYMBOL(debug_file_write);
|
|
|
|
int debug_file_close(struct debug_file *filp)
|
|
{
|
|
mutex_lock(&file_debug_lock);
|
|
if (filp->param.status == FILE_OPEN_FROM_KERNEL) {
|
|
filp->param.status = FILE_CLOSE_FROM_KERNEL;
|
|
mutex_unlock(&file_debug_lock);
|
|
|
|
mutex_lock(&files_info_lock);
|
|
kfifo_in(&files_info, &filp->param, sizeof(struct debug_file_param));
|
|
mutex_unlock(&files_info_lock);
|
|
} else {
|
|
if (filp->param.status != FILE_CLOSE_FROM_KERNEL &&
|
|
filp->param.status != FILE_CLOSE_READY) {
|
|
list_del(&filp->list);
|
|
list_add(&filp->list, &files_release_list);
|
|
schedule_delayed_work(&filp->release_work, msecs_to_jiffies(1));
|
|
}
|
|
mutex_unlock(&file_debug_lock);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(debug_file_close);
|
|
|
|
static int __init debug_file_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = kfifo_alloc(&files_info, sizeof(struct debug_file_param) * 100, GFP_KERNEL);
|
|
if (ret) {
|
|
pr_err("error kfifo_alloc files_info\n");
|
|
return ret;
|
|
}
|
|
|
|
proc_debug_file = proc_mkdir("debug_file", NULL);
|
|
if (!proc_debug_file) {
|
|
pr_err("failed to create debug_file proc entry\n");
|
|
goto err1;
|
|
}
|
|
|
|
proc_files_info = proc_create("files_info", 0444, proc_debug_file, &files_info_proc_ops);
|
|
if (!proc_files_info) {
|
|
pr_err("failed to create files_info proc file\n");
|
|
goto err2;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err2:
|
|
proc_remove(proc_debug_file);
|
|
err1:
|
|
kfifo_free(&files_info);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void __exit debug_file_exit(void)
|
|
{
|
|
while (1) {
|
|
if (list_empty(&files_list) && list_empty(&files_release_list))
|
|
break;
|
|
ssleep(1);
|
|
}
|
|
|
|
proc_remove(proc_debug_file);
|
|
kfifo_free(&files_info);
|
|
}
|
|
|
|
module_init(debug_file_init);
|
|
module_exit(debug_file_exit);
|
|
MODULE_LICENSE("GPL v2");
|