From ea08eeccae9297f7aabd2ef7f0c2517ac4549acc Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Tue, 20 Feb 2024 01:18:26 +0000
Subject: [PATCH] write in 30M
---
kernel/drivers/md/dm-user.c | 184 +++++++++++++++++++++++++++++++++++++++++----
1 files changed, 167 insertions(+), 17 deletions(-)
diff --git a/kernel/drivers/md/dm-user.c b/kernel/drivers/md/dm-user.c
index 7593467..a8d7718 100644
--- a/kernel/drivers/md/dm-user.c
+++ b/kernel/drivers/md/dm-user.c
@@ -15,10 +15,17 @@
#include <linux/poll.h>
#include <linux/uio.h>
#include <linux/wait.h>
+#include <linux/workqueue.h>
#define DM_MSG_PREFIX "user"
#define MAX_OUTSTANDING_MESSAGES 128
+
+static unsigned int daemon_timeout_msec = 4000;
+module_param_named(dm_user_daemon_timeout_msec, daemon_timeout_msec, uint,
+ 0644);
+MODULE_PARM_DESC(dm_user_daemon_timeout_msec,
+ "IO Timeout in msec if daemon does not process");
/*
* dm-user uses four structures:
@@ -80,6 +87,10 @@
*/
u64 return_type;
u64 return_flags;
+
+ struct delayed_work work;
+ bool delayed;
+ struct target *t;
};
struct target {
@@ -132,6 +143,7 @@
*/
struct kref references;
int dm_destroyed;
+ bool daemon_terminated;
};
struct channel {
@@ -171,6 +183,91 @@
*/
struct message scratch_message_from_user;
};
+
+static void message_kill(struct message *m, mempool_t *pool)
+{
+ m->bio->bi_status = BLK_STS_IOERR;
+ bio_endio(m->bio);
+ mempool_free(m, pool);
+}
+
+static inline bool is_user_space_thread_present(struct target *t)
+{
+ lockdep_assert_held(&t->lock);
+ return (kref_read(&t->references) > 1);
+}
+
+static void process_delayed_work(struct work_struct *work)
+{
+ struct delayed_work *del_work = to_delayed_work(work);
+ struct message *msg = container_of(del_work, struct message, work);
+
+ struct target *t = msg->t;
+
+ mutex_lock(&t->lock);
+
+ /*
+ * There is a atleast one thread to process the IO.
+ */
+ if (is_user_space_thread_present(t)) {
+ mutex_unlock(&t->lock);
+ return;
+ }
+
+ /*
+ * Terminate the IO with an error
+ */
+ list_del(&msg->to_user);
+ pr_err("I/O error: sector %llu: no user-space daemon for %s target\n",
+ msg->bio->bi_iter.bi_sector,
+ t->miscdev.name);
+ message_kill(msg, &t->message_pool);
+ mutex_unlock(&t->lock);
+}
+
+static void enqueue_delayed_work(struct message *m, bool is_delay)
+{
+ unsigned long delay = 0;
+
+ m->delayed = true;
+ INIT_DELAYED_WORK(&m->work, process_delayed_work);
+
+ /*
+ * Snapuserd daemon is the user-space process
+ * which processes IO request from dm-user
+ * when OTA is applied. Per the current design,
+ * when a dm-user target is created, daemon
+ * attaches to target and starts processing
+ * the IO's. Daemon is terminated only when
+ * dm-user target is destroyed.
+ *
+ * If for some reason, daemon crashes or terminates early,
+ * without destroying the dm-user target; then
+ * there is no mechanism to restart the daemon
+ * and start processing the IO's from the same target.
+ * Theoretically, it is possible but that infrastructure
+ * doesn't exist in the android ecosystem.
+ *
+ * Thus, when the daemon terminates, there is no way the IO's
+ * issued on that target will be processed. Hence,
+ * we set the delay to 0 and fail the IO's immediately.
+ *
+ * On the other hand, when a new dm-user target is created,
+ * we wait for the daemon to get attached for the first time.
+ * This primarily happens when init first stage spins up
+ * the daemon. At this point, since the snapshot device is mounted
+ * of a root filesystem, dm-user target may receive IO request
+ * even though daemon is not fully launched. We don't want
+ * to fail those IO requests immediately. Thus, we queue these
+ * requests with a timeout so that daemon is ready to process
+ * those IO requests. Again, if the daemon fails to launch within
+ * the timeout period, then IO's will be failed.
+ */
+ if (is_delay)
+ delay = msecs_to_jiffies(daemon_timeout_msec);
+
+ queue_delayed_work(system_wq, &m->work, delay);
+}
static inline struct target *target_from_target(struct dm_target *target)
{
@@ -500,7 +597,25 @@
return NULL;
m = list_first_entry(&t->to_user, struct message, to_user);
+
list_del(&m->to_user);
+
+ /*
+ * If the IO was queued to workqueue since there
+ * was no daemon to service the IO, then we
+ * will have to cancel the delayed work as the
+ * IO will be processed by this user-space thread.
+ *
+ * If the delayed work was already picked up for
+ * processing, then wait for it to complete. Note
+ * that the IO will not be terminated by the work
+ * queue thread.
+ */
+ if (unlikely(m->delayed)) {
+ mutex_unlock(&t->lock);
+ cancel_delayed_work_sync(&m->work);
+ mutex_lock(&t->lock);
+ }
return m;
}
@@ -522,26 +637,18 @@
return NULL;
}
-static void message_kill(struct message *m, mempool_t *pool)
-{
- m->bio->bi_status = BLK_STS_IOERR;
- bio_endio(m->bio);
- bio_put(m->bio);
- mempool_free(m, pool);
-}
-
/*
* Returns 0 when there is no work left to do. This must be callable without
* holding the target lock, as it is part of the waitqueue's check expression.
* When called without the lock it may spuriously indicate there is remaining
* work, but when called with the lock it must be accurate.
*/
-static int target_poll(struct target *t)
+int target_poll(struct target *t)
{
return !list_empty(&t->to_user) || t->dm_destroyed;
}
-static void target_release(struct kref *ref)
+void target_release(struct kref *ref)
{
struct target *t = container_of(ref, struct target, references);
struct list_head *cur, *tmp;
@@ -552,8 +659,18 @@
* there are and will never be any channels.
*/
list_for_each_safe (cur, tmp, &t->to_user) {
- message_kill(list_entry(cur, struct message, to_user),
- &t->message_pool);
+ struct message *m = list_entry(cur, struct message, to_user);
+
+ if (unlikely(m->delayed)) {
+ bool ret;
+
+ mutex_unlock(&t->lock);
+ ret = cancel_delayed_work_sync(&m->work);
+ mutex_lock(&t->lock);
+ if (!ret)
+ continue;
+ }
+ message_kill(m, &t->message_pool);
}
mempool_exit(&t->message_pool);
@@ -562,7 +679,7 @@
kfree(t);
}
-static void target_put(struct target *t)
+void target_put(struct target *t)
{
/*
* This both releases a reference to the target and the lock. We leave
@@ -571,8 +688,31 @@
*/
lockdep_assert_held(&t->lock);
- if (!kref_put(&t->references, target_release))
+ if (!kref_put(&t->references, target_release)) {
+ /*
+ * User-space thread is getting terminated.
+ * We need to scan the list for all those
+ * pending IO's which were not processed yet
+ * and put them back to work-queue for delayed
+ * processing.
+ */
+ if (!is_user_space_thread_present(t)) {
+ struct list_head *cur, *tmp;
+
+ list_for_each_safe(cur, tmp, &t->to_user) {
+ struct message *m = list_entry(cur,
+ struct message,
+ to_user);
+ if (!m->delayed)
+ enqueue_delayed_work(m, false);
+ }
+ /*
+ * Daemon attached to this target is terminated.
+ */
+ t->daemon_terminated = true;
+ }
mutex_unlock(&t->lock);
+ }
}
static struct channel *channel_alloc(struct target *t)
@@ -593,7 +733,7 @@
return c;
}
-static void channel_free(struct channel *c)
+void channel_free(struct channel *c)
{
struct list_head *cur, *tmp;
@@ -848,7 +988,6 @@
*/
WARN_ON(bio_size(c->cur_from_user->bio) != 0);
bio_endio(c->cur_from_user->bio);
- bio_put(c->cur_from_user->bio);
/*
* We don't actually need to take the target lock here, as all
@@ -917,6 +1056,7 @@
*/
kref_init(&t->references);
+ t->daemon_terminated = false;
mutex_init(&t->lock);
init_waitqueue_head(&t->wq);
INIT_LIST_HEAD(&t->to_user);
@@ -1085,7 +1225,6 @@
return DM_MAPIO_REQUEUE;
}
- bio_get(bio);
entry->msg.type = bio_type_to_user_type(bio);
entry->msg.flags = bio_flags_to_user_flags(bio);
entry->msg.sector = bio->bi_iter.bi_sector;
@@ -1095,9 +1234,20 @@
entry->total_to_user = bio_bytes_needed_to_user(bio);
entry->posn_from_user = 0;
entry->total_from_user = bio_bytes_needed_from_user(bio);
+ entry->delayed = false;
+ entry->t = t;
/* Pairs with the barrier in dev_read() */
smp_wmb();
list_add_tail(&entry->to_user, &t->to_user);
+
+ /*
+ * If there is no daemon to process the IO's,
+ * queue these messages into a workqueue with
+ * a timeout.
+ */
+ if (!is_user_space_thread_present(t))
+ enqueue_delayed_work(entry, !t->daemon_terminated);
+
wake_up_interruptible(&t->wq);
mutex_unlock(&t->lock);
return DM_MAPIO_SUBMITTED;
--
Gitblit v1.6.2