Files
android_kernel_samsung_sm8750/fs/fuse/watchdog.c
2025-08-11 13:49:01 +02:00

168 lines
3.9 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 Yoonho Shin <yoonho.shin@samsung.com>
*/
#include "fuse_i.h"
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/freezer.h>
#ifndef ST_LOG
#define ST_LOG(fmt, ...) pr_err(fmt, ##__VA_ARGS__)
#endif
#define MAX_WAIT_FUSE_ABORT 2
struct fuse_daemon_watchdog_data {
struct fuse_conn *fc;
struct task_struct *group_leader_daemon;
};
static int watch_daemon_status(void *data)
{
struct fuse_daemon_watchdog_data *fwd = data;
struct fuse_conn *fc = fwd->fc;
struct task_struct *daemon = fwd->group_leader_daemon;
int cnt = 0;
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
ST_LOG("<%s> dev = %u:%u fuse watchdog start\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
set_freezable();
while (cnt < MAX_WAIT_FUSE_ABORT) {
/*
* Use conditional wait to prevent race between system freezing
* request and sleep.
*/
wait_event_interruptible_timeout(wq,
kthread_should_stop() || freezing(current) ||
daemon->exit_state == EXIT_DEAD,
msecs_to_jiffies(10 * MSEC_PER_SEC));
if (kthread_should_stop()) {
ST_LOG("<%s> dev = %u:%u kthread_should_stop\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
break;
}
/*
* The fuse daemon process have exited normally.
*/
if (daemon->exit_state == EXIT_DEAD) {
ST_LOG("<%s> dev = %u:%u EXIT_DEAD\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
break;
}
if (try_to_freeze())
continue;
/*
* P231024-04181
* : The group_leader is a zombie, and other threads have exited.
*
* The group leader has completed do_exit(). However, the binder
* thread must call do_notify_parent() so that the parent process
* can clean up the MediaProvider process.
*
* do_exit()
* exit_notify()
* do_notify_parent()
*/
if (daemon->exit_state == EXIT_ZOMBIE) {
ST_LOG("<%s> dev = %u:%u EXIT_ZOMBIE\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
cnt++;
continue;
}
/*
* P231128-07435
* : Waiting for the binder thread to finish core dump.
*
* all threads wait at
* do_exit()
* coredump_task_exit()
*/
if (daemon->flags & PF_POSTCOREDUMP) {
ST_LOG("<%s> dev = %u:%u PF_POSTCOREDUMP\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
cnt++;
continue;
}
}
if (cnt >= MAX_WAIT_FUSE_ABORT) {
WARN_ON(cnt > MAX_WAIT_FUSE_ABORT);
ST_LOG("<%s> dev = %u:%u fuse watchdog abort_fuse_conn\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
fuse_abort_conn(fc);
}
ST_LOG("<%s> dev = %u:%u fuse watchdog end\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
put_task_struct(daemon);
kfree(fwd);
return 0;
}
// TODO: Temp code. Need to change to mount option in the future.
static bool am_i_mp(void)
{
struct task_struct *gl = get_task_struct(current->group_leader);
bool ret = false;
if (!strncmp(gl->comm, "rs.media.module", strlen("rs.media.module")))
ret = true;
put_task_struct(gl);
return ret;
}
void fuse_daemon_watchdog_start(struct fuse_conn *fc)
{
struct fuse_daemon_watchdog_data *fwd;
struct task_struct *watchdog_thread;
if (!am_i_mp())
return;
fwd = kmalloc(sizeof(struct fuse_daemon_watchdog_data), GFP_KERNEL);
if (!fwd) {
ST_LOG("<%s> dev = %u:%u failed to alloc watchdog data\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
return;
}
fwd->fc = fc;
fwd->group_leader_daemon = get_task_struct(current->group_leader);
watchdog_thread = kthread_run(watch_daemon_status, fwd,
"fuse-wd%u:%u", MAJOR(fc->dev), MINOR(fc->dev));
if (IS_ERR(watchdog_thread)) {
ST_LOG("<%s> dev = %u:%u failed to start watchdog thread\n",
__func__, MAJOR(fc->dev), MINOR(fc->dev));
put_task_struct(fwd->group_leader_daemon);
kfree(fwd);
return;
}
fc->watchdog_thread = get_task_struct(watchdog_thread);
}
void fuse_daemon_watchdog_stop(struct fuse_conn *fc)
{
if (fc->watchdog_thread) {
kthread_stop(fc->watchdog_thread);
put_task_struct(fc->watchdog_thread);
}
}