Files
kernel_common_drivers/drivers/debug/debug_file.c
T
qinglin.li 0afabe230c debug file: add debug file function [1/2]
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
2022-08-18 20:38:55 +08:00

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(&param, 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");