xref: /kernel/linux/linux-5.10/fs/btrfs/zstd.c (revision 8c2ecf20)
18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2016-present, Facebook, Inc.
48c2ecf20Sopenharmony_ci * All rights reserved.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/bio.h>
98c2ecf20Sopenharmony_ci#include <linux/bitmap.h>
108c2ecf20Sopenharmony_ci#include <linux/err.h>
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/mm.h>
148c2ecf20Sopenharmony_ci#include <linux/sched/mm.h>
158c2ecf20Sopenharmony_ci#include <linux/pagemap.h>
168c2ecf20Sopenharmony_ci#include <linux/refcount.h>
178c2ecf20Sopenharmony_ci#include <linux/sched.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <linux/zstd.h>
208c2ecf20Sopenharmony_ci#include "misc.h"
218c2ecf20Sopenharmony_ci#include "compression.h"
228c2ecf20Sopenharmony_ci#include "ctree.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define ZSTD_BTRFS_MAX_WINDOWLOG 17
258c2ecf20Sopenharmony_ci#define ZSTD_BTRFS_MAX_INPUT (1 << ZSTD_BTRFS_MAX_WINDOWLOG)
268c2ecf20Sopenharmony_ci#define ZSTD_BTRFS_DEFAULT_LEVEL 3
278c2ecf20Sopenharmony_ci#define ZSTD_BTRFS_MAX_LEVEL 15
288c2ecf20Sopenharmony_ci/* 307s to avoid pathologically clashing with transaction commit */
298c2ecf20Sopenharmony_ci#define ZSTD_BTRFS_RECLAIM_JIFFIES (307 * HZ)
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic ZSTD_parameters zstd_get_btrfs_parameters(unsigned int level,
328c2ecf20Sopenharmony_ci						 size_t src_len)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	ZSTD_parameters params = ZSTD_getParams(level, src_len, 0);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	if (params.cParams.windowLog > ZSTD_BTRFS_MAX_WINDOWLOG)
378c2ecf20Sopenharmony_ci		params.cParams.windowLog = ZSTD_BTRFS_MAX_WINDOWLOG;
388c2ecf20Sopenharmony_ci	WARN_ON(src_len > ZSTD_BTRFS_MAX_INPUT);
398c2ecf20Sopenharmony_ci	return params;
408c2ecf20Sopenharmony_ci}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistruct workspace {
438c2ecf20Sopenharmony_ci	void *mem;
448c2ecf20Sopenharmony_ci	size_t size;
458c2ecf20Sopenharmony_ci	char *buf;
468c2ecf20Sopenharmony_ci	unsigned int level;
478c2ecf20Sopenharmony_ci	unsigned int req_level;
488c2ecf20Sopenharmony_ci	unsigned long last_used; /* jiffies */
498c2ecf20Sopenharmony_ci	struct list_head list;
508c2ecf20Sopenharmony_ci	struct list_head lru_list;
518c2ecf20Sopenharmony_ci	ZSTD_inBuffer in_buf;
528c2ecf20Sopenharmony_ci	ZSTD_outBuffer out_buf;
538c2ecf20Sopenharmony_ci};
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci/*
568c2ecf20Sopenharmony_ci * Zstd Workspace Management
578c2ecf20Sopenharmony_ci *
588c2ecf20Sopenharmony_ci * Zstd workspaces have different memory requirements depending on the level.
598c2ecf20Sopenharmony_ci * The zstd workspaces are managed by having individual lists for each level
608c2ecf20Sopenharmony_ci * and a global lru.  Forward progress is maintained by protecting a max level
618c2ecf20Sopenharmony_ci * workspace.
628c2ecf20Sopenharmony_ci *
638c2ecf20Sopenharmony_ci * Getting a workspace is done by using the bitmap to identify the levels that
648c2ecf20Sopenharmony_ci * have available workspaces and scans up.  This lets us recycle higher level
658c2ecf20Sopenharmony_ci * workspaces because of the monotonic memory guarantee.  A workspace's
668c2ecf20Sopenharmony_ci * last_used is only updated if it is being used by the corresponding memory
678c2ecf20Sopenharmony_ci * level.  Putting a workspace involves adding it back to the appropriate places
688c2ecf20Sopenharmony_ci * and adding it back to the lru if necessary.
698c2ecf20Sopenharmony_ci *
708c2ecf20Sopenharmony_ci * A timer is used to reclaim workspaces if they have not been used for
718c2ecf20Sopenharmony_ci * ZSTD_BTRFS_RECLAIM_JIFFIES.  This helps keep only active workspaces around.
728c2ecf20Sopenharmony_ci * The upper bound is provided by the workqueue limit which is 2 (percpu limit).
738c2ecf20Sopenharmony_ci */
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistruct zstd_workspace_manager {
768c2ecf20Sopenharmony_ci	const struct btrfs_compress_op *ops;
778c2ecf20Sopenharmony_ci	spinlock_t lock;
788c2ecf20Sopenharmony_ci	struct list_head lru_list;
798c2ecf20Sopenharmony_ci	struct list_head idle_ws[ZSTD_BTRFS_MAX_LEVEL];
808c2ecf20Sopenharmony_ci	unsigned long active_map;
818c2ecf20Sopenharmony_ci	wait_queue_head_t wait;
828c2ecf20Sopenharmony_ci	struct timer_list timer;
838c2ecf20Sopenharmony_ci};
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic struct zstd_workspace_manager wsm;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic size_t zstd_ws_mem_sizes[ZSTD_BTRFS_MAX_LEVEL];
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic inline struct workspace *list_to_workspace(struct list_head *list)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	return container_of(list, struct workspace, list);
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_civoid zstd_free_workspace(struct list_head *ws);
958c2ecf20Sopenharmony_cistruct list_head *zstd_alloc_workspace(unsigned int level);
968c2ecf20Sopenharmony_ci/*
978c2ecf20Sopenharmony_ci * zstd_reclaim_timer_fn - reclaim timer
988c2ecf20Sopenharmony_ci * @t: timer
998c2ecf20Sopenharmony_ci *
1008c2ecf20Sopenharmony_ci * This scans the lru_list and attempts to reclaim any workspace that hasn't
1018c2ecf20Sopenharmony_ci * been used for ZSTD_BTRFS_RECLAIM_JIFFIES.
1028c2ecf20Sopenharmony_ci */
1038c2ecf20Sopenharmony_cistatic void zstd_reclaim_timer_fn(struct timer_list *timer)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	unsigned long reclaim_threshold = jiffies - ZSTD_BTRFS_RECLAIM_JIFFIES;
1068c2ecf20Sopenharmony_ci	struct list_head *pos, *next;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	spin_lock_bh(&wsm.lock);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (list_empty(&wsm.lru_list)) {
1118c2ecf20Sopenharmony_ci		spin_unlock_bh(&wsm.lock);
1128c2ecf20Sopenharmony_ci		return;
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	list_for_each_prev_safe(pos, next, &wsm.lru_list) {
1168c2ecf20Sopenharmony_ci		struct workspace *victim = container_of(pos, struct workspace,
1178c2ecf20Sopenharmony_ci							lru_list);
1188c2ecf20Sopenharmony_ci		unsigned int level;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci		if (time_after(victim->last_used, reclaim_threshold))
1218c2ecf20Sopenharmony_ci			break;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci		/* workspace is in use */
1248c2ecf20Sopenharmony_ci		if (victim->req_level)
1258c2ecf20Sopenharmony_ci			continue;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci		level = victim->level;
1288c2ecf20Sopenharmony_ci		list_del(&victim->lru_list);
1298c2ecf20Sopenharmony_ci		list_del(&victim->list);
1308c2ecf20Sopenharmony_ci		zstd_free_workspace(&victim->list);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci		if (list_empty(&wsm.idle_ws[level - 1]))
1338c2ecf20Sopenharmony_ci			clear_bit(level - 1, &wsm.active_map);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	if (!list_empty(&wsm.lru_list))
1388c2ecf20Sopenharmony_ci		mod_timer(&wsm.timer, jiffies + ZSTD_BTRFS_RECLAIM_JIFFIES);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	spin_unlock_bh(&wsm.lock);
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci/*
1448c2ecf20Sopenharmony_ci * zstd_calc_ws_mem_sizes - calculate monotonic memory bounds
1458c2ecf20Sopenharmony_ci *
1468c2ecf20Sopenharmony_ci * It is possible based on the level configurations that a higher level
1478c2ecf20Sopenharmony_ci * workspace uses less memory than a lower level workspace.  In order to reuse
1488c2ecf20Sopenharmony_ci * workspaces, this must be made a monotonic relationship.  This precomputes
1498c2ecf20Sopenharmony_ci * the required memory for each level and enforces the monotonicity between
1508c2ecf20Sopenharmony_ci * level and memory required.
1518c2ecf20Sopenharmony_ci */
1528c2ecf20Sopenharmony_cistatic void zstd_calc_ws_mem_sizes(void)
1538c2ecf20Sopenharmony_ci{
1548c2ecf20Sopenharmony_ci	size_t max_size = 0;
1558c2ecf20Sopenharmony_ci	unsigned int level;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	for (level = 1; level <= ZSTD_BTRFS_MAX_LEVEL; level++) {
1588c2ecf20Sopenharmony_ci		ZSTD_parameters params =
1598c2ecf20Sopenharmony_ci			zstd_get_btrfs_parameters(level, ZSTD_BTRFS_MAX_INPUT);
1608c2ecf20Sopenharmony_ci		size_t level_size =
1618c2ecf20Sopenharmony_ci			max_t(size_t,
1628c2ecf20Sopenharmony_ci			      ZSTD_CStreamWorkspaceBound(params.cParams),
1638c2ecf20Sopenharmony_ci			      ZSTD_DStreamWorkspaceBound(ZSTD_BTRFS_MAX_INPUT));
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci		max_size = max_t(size_t, max_size, level_size);
1668c2ecf20Sopenharmony_ci		zstd_ws_mem_sizes[level - 1] = max_size;
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_civoid zstd_init_workspace_manager(void)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct list_head *ws;
1738c2ecf20Sopenharmony_ci	int i;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	zstd_calc_ws_mem_sizes();
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	wsm.ops = &btrfs_zstd_compress;
1788c2ecf20Sopenharmony_ci	spin_lock_init(&wsm.lock);
1798c2ecf20Sopenharmony_ci	init_waitqueue_head(&wsm.wait);
1808c2ecf20Sopenharmony_ci	timer_setup(&wsm.timer, zstd_reclaim_timer_fn, 0);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&wsm.lru_list);
1838c2ecf20Sopenharmony_ci	for (i = 0; i < ZSTD_BTRFS_MAX_LEVEL; i++)
1848c2ecf20Sopenharmony_ci		INIT_LIST_HEAD(&wsm.idle_ws[i]);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	ws = zstd_alloc_workspace(ZSTD_BTRFS_MAX_LEVEL);
1878c2ecf20Sopenharmony_ci	if (IS_ERR(ws)) {
1888c2ecf20Sopenharmony_ci		pr_warn(
1898c2ecf20Sopenharmony_ci		"BTRFS: cannot preallocate zstd compression workspace\n");
1908c2ecf20Sopenharmony_ci	} else {
1918c2ecf20Sopenharmony_ci		set_bit(ZSTD_BTRFS_MAX_LEVEL - 1, &wsm.active_map);
1928c2ecf20Sopenharmony_ci		list_add(ws, &wsm.idle_ws[ZSTD_BTRFS_MAX_LEVEL - 1]);
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_civoid zstd_cleanup_workspace_manager(void)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	struct workspace *workspace;
1998c2ecf20Sopenharmony_ci	int i;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	spin_lock_bh(&wsm.lock);
2028c2ecf20Sopenharmony_ci	for (i = 0; i < ZSTD_BTRFS_MAX_LEVEL; i++) {
2038c2ecf20Sopenharmony_ci		while (!list_empty(&wsm.idle_ws[i])) {
2048c2ecf20Sopenharmony_ci			workspace = container_of(wsm.idle_ws[i].next,
2058c2ecf20Sopenharmony_ci						 struct workspace, list);
2068c2ecf20Sopenharmony_ci			list_del(&workspace->list);
2078c2ecf20Sopenharmony_ci			list_del(&workspace->lru_list);
2088c2ecf20Sopenharmony_ci			zstd_free_workspace(&workspace->list);
2098c2ecf20Sopenharmony_ci		}
2108c2ecf20Sopenharmony_ci	}
2118c2ecf20Sopenharmony_ci	spin_unlock_bh(&wsm.lock);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	del_timer_sync(&wsm.timer);
2148c2ecf20Sopenharmony_ci}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci/*
2178c2ecf20Sopenharmony_ci * zstd_find_workspace - find workspace
2188c2ecf20Sopenharmony_ci * @level: compression level
2198c2ecf20Sopenharmony_ci *
2208c2ecf20Sopenharmony_ci * This iterates over the set bits in the active_map beginning at the requested
2218c2ecf20Sopenharmony_ci * compression level.  This lets us utilize already allocated workspaces before
2228c2ecf20Sopenharmony_ci * allocating a new one.  If the workspace is of a larger size, it is used, but
2238c2ecf20Sopenharmony_ci * the place in the lru_list and last_used times are not updated.  This is to
2248c2ecf20Sopenharmony_ci * offer the opportunity to reclaim the workspace in favor of allocating an
2258c2ecf20Sopenharmony_ci * appropriately sized one in the future.
2268c2ecf20Sopenharmony_ci */
2278c2ecf20Sopenharmony_cistatic struct list_head *zstd_find_workspace(unsigned int level)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	struct list_head *ws;
2308c2ecf20Sopenharmony_ci	struct workspace *workspace;
2318c2ecf20Sopenharmony_ci	int i = level - 1;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	spin_lock_bh(&wsm.lock);
2348c2ecf20Sopenharmony_ci	for_each_set_bit_from(i, &wsm.active_map, ZSTD_BTRFS_MAX_LEVEL) {
2358c2ecf20Sopenharmony_ci		if (!list_empty(&wsm.idle_ws[i])) {
2368c2ecf20Sopenharmony_ci			ws = wsm.idle_ws[i].next;
2378c2ecf20Sopenharmony_ci			workspace = list_to_workspace(ws);
2388c2ecf20Sopenharmony_ci			list_del_init(ws);
2398c2ecf20Sopenharmony_ci			/* keep its place if it's a lower level using this */
2408c2ecf20Sopenharmony_ci			workspace->req_level = level;
2418c2ecf20Sopenharmony_ci			if (level == workspace->level)
2428c2ecf20Sopenharmony_ci				list_del(&workspace->lru_list);
2438c2ecf20Sopenharmony_ci			if (list_empty(&wsm.idle_ws[i]))
2448c2ecf20Sopenharmony_ci				clear_bit(i, &wsm.active_map);
2458c2ecf20Sopenharmony_ci			spin_unlock_bh(&wsm.lock);
2468c2ecf20Sopenharmony_ci			return ws;
2478c2ecf20Sopenharmony_ci		}
2488c2ecf20Sopenharmony_ci	}
2498c2ecf20Sopenharmony_ci	spin_unlock_bh(&wsm.lock);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	return NULL;
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci/*
2558c2ecf20Sopenharmony_ci * zstd_get_workspace - zstd's get_workspace
2568c2ecf20Sopenharmony_ci * @level: compression level
2578c2ecf20Sopenharmony_ci *
2588c2ecf20Sopenharmony_ci * If @level is 0, then any compression level can be used.  Therefore, we begin
2598c2ecf20Sopenharmony_ci * scanning from 1.  We first scan through possible workspaces and then after
2608c2ecf20Sopenharmony_ci * attempt to allocate a new workspace.  If we fail to allocate one due to
2618c2ecf20Sopenharmony_ci * memory pressure, go to sleep waiting for the max level workspace to free up.
2628c2ecf20Sopenharmony_ci */
2638c2ecf20Sopenharmony_cistruct list_head *zstd_get_workspace(unsigned int level)
2648c2ecf20Sopenharmony_ci{
2658c2ecf20Sopenharmony_ci	struct list_head *ws;
2668c2ecf20Sopenharmony_ci	unsigned int nofs_flag;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	/* level == 0 means we can use any workspace */
2698c2ecf20Sopenharmony_ci	if (!level)
2708c2ecf20Sopenharmony_ci		level = 1;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ciagain:
2738c2ecf20Sopenharmony_ci	ws = zstd_find_workspace(level);
2748c2ecf20Sopenharmony_ci	if (ws)
2758c2ecf20Sopenharmony_ci		return ws;
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	nofs_flag = memalloc_nofs_save();
2788c2ecf20Sopenharmony_ci	ws = zstd_alloc_workspace(level);
2798c2ecf20Sopenharmony_ci	memalloc_nofs_restore(nofs_flag);
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	if (IS_ERR(ws)) {
2828c2ecf20Sopenharmony_ci		DEFINE_WAIT(wait);
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci		prepare_to_wait(&wsm.wait, &wait, TASK_UNINTERRUPTIBLE);
2858c2ecf20Sopenharmony_ci		schedule();
2868c2ecf20Sopenharmony_ci		finish_wait(&wsm.wait, &wait);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci		goto again;
2898c2ecf20Sopenharmony_ci	}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	return ws;
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci/*
2958c2ecf20Sopenharmony_ci * zstd_put_workspace - zstd put_workspace
2968c2ecf20Sopenharmony_ci * @ws: list_head for the workspace
2978c2ecf20Sopenharmony_ci *
2988c2ecf20Sopenharmony_ci * When putting back a workspace, we only need to update the LRU if we are of
2998c2ecf20Sopenharmony_ci * the requested compression level.  Here is where we continue to protect the
3008c2ecf20Sopenharmony_ci * max level workspace or update last_used accordingly.  If the reclaim timer
3018c2ecf20Sopenharmony_ci * isn't set, it is also set here.  Only the max level workspace tries and wakes
3028c2ecf20Sopenharmony_ci * up waiting workspaces.
3038c2ecf20Sopenharmony_ci */
3048c2ecf20Sopenharmony_civoid zstd_put_workspace(struct list_head *ws)
3058c2ecf20Sopenharmony_ci{
3068c2ecf20Sopenharmony_ci	struct workspace *workspace = list_to_workspace(ws);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	spin_lock_bh(&wsm.lock);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci	/* A node is only taken off the lru if we are the corresponding level */
3118c2ecf20Sopenharmony_ci	if (workspace->req_level == workspace->level) {
3128c2ecf20Sopenharmony_ci		/* Hide a max level workspace from reclaim */
3138c2ecf20Sopenharmony_ci		if (list_empty(&wsm.idle_ws[ZSTD_BTRFS_MAX_LEVEL - 1])) {
3148c2ecf20Sopenharmony_ci			INIT_LIST_HEAD(&workspace->lru_list);
3158c2ecf20Sopenharmony_ci		} else {
3168c2ecf20Sopenharmony_ci			workspace->last_used = jiffies;
3178c2ecf20Sopenharmony_ci			list_add(&workspace->lru_list, &wsm.lru_list);
3188c2ecf20Sopenharmony_ci			if (!timer_pending(&wsm.timer))
3198c2ecf20Sopenharmony_ci				mod_timer(&wsm.timer,
3208c2ecf20Sopenharmony_ci					  jiffies + ZSTD_BTRFS_RECLAIM_JIFFIES);
3218c2ecf20Sopenharmony_ci		}
3228c2ecf20Sopenharmony_ci	}
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci	set_bit(workspace->level - 1, &wsm.active_map);
3258c2ecf20Sopenharmony_ci	list_add(&workspace->list, &wsm.idle_ws[workspace->level - 1]);
3268c2ecf20Sopenharmony_ci	workspace->req_level = 0;
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	spin_unlock_bh(&wsm.lock);
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	if (workspace->level == ZSTD_BTRFS_MAX_LEVEL)
3318c2ecf20Sopenharmony_ci		cond_wake_up(&wsm.wait);
3328c2ecf20Sopenharmony_ci}
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_civoid zstd_free_workspace(struct list_head *ws)
3358c2ecf20Sopenharmony_ci{
3368c2ecf20Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	kvfree(workspace->mem);
3398c2ecf20Sopenharmony_ci	kfree(workspace->buf);
3408c2ecf20Sopenharmony_ci	kfree(workspace);
3418c2ecf20Sopenharmony_ci}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_cistruct list_head *zstd_alloc_workspace(unsigned int level)
3448c2ecf20Sopenharmony_ci{
3458c2ecf20Sopenharmony_ci	struct workspace *workspace;
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	workspace = kzalloc(sizeof(*workspace), GFP_KERNEL);
3488c2ecf20Sopenharmony_ci	if (!workspace)
3498c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	workspace->size = zstd_ws_mem_sizes[level - 1];
3528c2ecf20Sopenharmony_ci	workspace->level = level;
3538c2ecf20Sopenharmony_ci	workspace->req_level = level;
3548c2ecf20Sopenharmony_ci	workspace->last_used = jiffies;
3558c2ecf20Sopenharmony_ci	workspace->mem = kvmalloc(workspace->size, GFP_KERNEL);
3568c2ecf20Sopenharmony_ci	workspace->buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
3578c2ecf20Sopenharmony_ci	if (!workspace->mem || !workspace->buf)
3588c2ecf20Sopenharmony_ci		goto fail;
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&workspace->list);
3618c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&workspace->lru_list);
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	return &workspace->list;
3648c2ecf20Sopenharmony_cifail:
3658c2ecf20Sopenharmony_ci	zstd_free_workspace(&workspace->list);
3668c2ecf20Sopenharmony_ci	return ERR_PTR(-ENOMEM);
3678c2ecf20Sopenharmony_ci}
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ciint zstd_compress_pages(struct list_head *ws, struct address_space *mapping,
3708c2ecf20Sopenharmony_ci		u64 start, struct page **pages, unsigned long *out_pages,
3718c2ecf20Sopenharmony_ci		unsigned long *total_in, unsigned long *total_out)
3728c2ecf20Sopenharmony_ci{
3738c2ecf20Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
3748c2ecf20Sopenharmony_ci	ZSTD_CStream *stream;
3758c2ecf20Sopenharmony_ci	int ret = 0;
3768c2ecf20Sopenharmony_ci	int nr_pages = 0;
3778c2ecf20Sopenharmony_ci	struct page *in_page = NULL;  /* The current page to read */
3788c2ecf20Sopenharmony_ci	struct page *out_page = NULL; /* The current page to write to */
3798c2ecf20Sopenharmony_ci	unsigned long tot_in = 0;
3808c2ecf20Sopenharmony_ci	unsigned long tot_out = 0;
3818c2ecf20Sopenharmony_ci	unsigned long len = *total_out;
3828c2ecf20Sopenharmony_ci	const unsigned long nr_dest_pages = *out_pages;
3838c2ecf20Sopenharmony_ci	unsigned long max_out = nr_dest_pages * PAGE_SIZE;
3848c2ecf20Sopenharmony_ci	ZSTD_parameters params = zstd_get_btrfs_parameters(workspace->req_level,
3858c2ecf20Sopenharmony_ci							   len);
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	*out_pages = 0;
3888c2ecf20Sopenharmony_ci	*total_out = 0;
3898c2ecf20Sopenharmony_ci	*total_in = 0;
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci	/* Initialize the stream */
3928c2ecf20Sopenharmony_ci	stream = ZSTD_initCStream(params, len, workspace->mem,
3938c2ecf20Sopenharmony_ci			workspace->size);
3948c2ecf20Sopenharmony_ci	if (!stream) {
3958c2ecf20Sopenharmony_ci		pr_warn("BTRFS: ZSTD_initCStream failed\n");
3968c2ecf20Sopenharmony_ci		ret = -EIO;
3978c2ecf20Sopenharmony_ci		goto out;
3988c2ecf20Sopenharmony_ci	}
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci	/* map in the first page of input data */
4018c2ecf20Sopenharmony_ci	in_page = find_get_page(mapping, start >> PAGE_SHIFT);
4028c2ecf20Sopenharmony_ci	workspace->in_buf.src = kmap(in_page);
4038c2ecf20Sopenharmony_ci	workspace->in_buf.pos = 0;
4048c2ecf20Sopenharmony_ci	workspace->in_buf.size = min_t(size_t, len, PAGE_SIZE);
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	/* Allocate and map in the output buffer */
4088c2ecf20Sopenharmony_ci	out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM);
4098c2ecf20Sopenharmony_ci	if (out_page == NULL) {
4108c2ecf20Sopenharmony_ci		ret = -ENOMEM;
4118c2ecf20Sopenharmony_ci		goto out;
4128c2ecf20Sopenharmony_ci	}
4138c2ecf20Sopenharmony_ci	pages[nr_pages++] = out_page;
4148c2ecf20Sopenharmony_ci	workspace->out_buf.dst = kmap(out_page);
4158c2ecf20Sopenharmony_ci	workspace->out_buf.pos = 0;
4168c2ecf20Sopenharmony_ci	workspace->out_buf.size = min_t(size_t, max_out, PAGE_SIZE);
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci	while (1) {
4198c2ecf20Sopenharmony_ci		size_t ret2;
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci		ret2 = ZSTD_compressStream(stream, &workspace->out_buf,
4228c2ecf20Sopenharmony_ci				&workspace->in_buf);
4238c2ecf20Sopenharmony_ci		if (ZSTD_isError(ret2)) {
4248c2ecf20Sopenharmony_ci			pr_debug("BTRFS: ZSTD_compressStream returned %d\n",
4258c2ecf20Sopenharmony_ci					ZSTD_getErrorCode(ret2));
4268c2ecf20Sopenharmony_ci			ret = -EIO;
4278c2ecf20Sopenharmony_ci			goto out;
4288c2ecf20Sopenharmony_ci		}
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_ci		/* Check to see if we are making it bigger */
4318c2ecf20Sopenharmony_ci		if (tot_in + workspace->in_buf.pos > 8192 &&
4328c2ecf20Sopenharmony_ci				tot_in + workspace->in_buf.pos <
4338c2ecf20Sopenharmony_ci				tot_out + workspace->out_buf.pos) {
4348c2ecf20Sopenharmony_ci			ret = -E2BIG;
4358c2ecf20Sopenharmony_ci			goto out;
4368c2ecf20Sopenharmony_ci		}
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci		/* We've reached the end of our output range */
4398c2ecf20Sopenharmony_ci		if (workspace->out_buf.pos >= max_out) {
4408c2ecf20Sopenharmony_ci			tot_out += workspace->out_buf.pos;
4418c2ecf20Sopenharmony_ci			ret = -E2BIG;
4428c2ecf20Sopenharmony_ci			goto out;
4438c2ecf20Sopenharmony_ci		}
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_ci		/* Check if we need more output space */
4468c2ecf20Sopenharmony_ci		if (workspace->out_buf.pos == workspace->out_buf.size) {
4478c2ecf20Sopenharmony_ci			tot_out += PAGE_SIZE;
4488c2ecf20Sopenharmony_ci			max_out -= PAGE_SIZE;
4498c2ecf20Sopenharmony_ci			kunmap(out_page);
4508c2ecf20Sopenharmony_ci			if (nr_pages == nr_dest_pages) {
4518c2ecf20Sopenharmony_ci				out_page = NULL;
4528c2ecf20Sopenharmony_ci				ret = -E2BIG;
4538c2ecf20Sopenharmony_ci				goto out;
4548c2ecf20Sopenharmony_ci			}
4558c2ecf20Sopenharmony_ci			out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM);
4568c2ecf20Sopenharmony_ci			if (out_page == NULL) {
4578c2ecf20Sopenharmony_ci				ret = -ENOMEM;
4588c2ecf20Sopenharmony_ci				goto out;
4598c2ecf20Sopenharmony_ci			}
4608c2ecf20Sopenharmony_ci			pages[nr_pages++] = out_page;
4618c2ecf20Sopenharmony_ci			workspace->out_buf.dst = kmap(out_page);
4628c2ecf20Sopenharmony_ci			workspace->out_buf.pos = 0;
4638c2ecf20Sopenharmony_ci			workspace->out_buf.size = min_t(size_t, max_out,
4648c2ecf20Sopenharmony_ci							PAGE_SIZE);
4658c2ecf20Sopenharmony_ci		}
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci		/* We've reached the end of the input */
4688c2ecf20Sopenharmony_ci		if (workspace->in_buf.pos >= len) {
4698c2ecf20Sopenharmony_ci			tot_in += workspace->in_buf.pos;
4708c2ecf20Sopenharmony_ci			break;
4718c2ecf20Sopenharmony_ci		}
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci		/* Check if we need more input */
4748c2ecf20Sopenharmony_ci		if (workspace->in_buf.pos == workspace->in_buf.size) {
4758c2ecf20Sopenharmony_ci			tot_in += PAGE_SIZE;
4768c2ecf20Sopenharmony_ci			kunmap(in_page);
4778c2ecf20Sopenharmony_ci			put_page(in_page);
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci			start += PAGE_SIZE;
4808c2ecf20Sopenharmony_ci			len -= PAGE_SIZE;
4818c2ecf20Sopenharmony_ci			in_page = find_get_page(mapping, start >> PAGE_SHIFT);
4828c2ecf20Sopenharmony_ci			workspace->in_buf.src = kmap(in_page);
4838c2ecf20Sopenharmony_ci			workspace->in_buf.pos = 0;
4848c2ecf20Sopenharmony_ci			workspace->in_buf.size = min_t(size_t, len, PAGE_SIZE);
4858c2ecf20Sopenharmony_ci		}
4868c2ecf20Sopenharmony_ci	}
4878c2ecf20Sopenharmony_ci	while (1) {
4888c2ecf20Sopenharmony_ci		size_t ret2;
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci		ret2 = ZSTD_endStream(stream, &workspace->out_buf);
4918c2ecf20Sopenharmony_ci		if (ZSTD_isError(ret2)) {
4928c2ecf20Sopenharmony_ci			pr_debug("BTRFS: ZSTD_endStream returned %d\n",
4938c2ecf20Sopenharmony_ci					ZSTD_getErrorCode(ret2));
4948c2ecf20Sopenharmony_ci			ret = -EIO;
4958c2ecf20Sopenharmony_ci			goto out;
4968c2ecf20Sopenharmony_ci		}
4978c2ecf20Sopenharmony_ci		if (ret2 == 0) {
4988c2ecf20Sopenharmony_ci			tot_out += workspace->out_buf.pos;
4998c2ecf20Sopenharmony_ci			break;
5008c2ecf20Sopenharmony_ci		}
5018c2ecf20Sopenharmony_ci		if (workspace->out_buf.pos >= max_out) {
5028c2ecf20Sopenharmony_ci			tot_out += workspace->out_buf.pos;
5038c2ecf20Sopenharmony_ci			ret = -E2BIG;
5048c2ecf20Sopenharmony_ci			goto out;
5058c2ecf20Sopenharmony_ci		}
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_ci		tot_out += PAGE_SIZE;
5088c2ecf20Sopenharmony_ci		max_out -= PAGE_SIZE;
5098c2ecf20Sopenharmony_ci		kunmap(out_page);
5108c2ecf20Sopenharmony_ci		if (nr_pages == nr_dest_pages) {
5118c2ecf20Sopenharmony_ci			out_page = NULL;
5128c2ecf20Sopenharmony_ci			ret = -E2BIG;
5138c2ecf20Sopenharmony_ci			goto out;
5148c2ecf20Sopenharmony_ci		}
5158c2ecf20Sopenharmony_ci		out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM);
5168c2ecf20Sopenharmony_ci		if (out_page == NULL) {
5178c2ecf20Sopenharmony_ci			ret = -ENOMEM;
5188c2ecf20Sopenharmony_ci			goto out;
5198c2ecf20Sopenharmony_ci		}
5208c2ecf20Sopenharmony_ci		pages[nr_pages++] = out_page;
5218c2ecf20Sopenharmony_ci		workspace->out_buf.dst = kmap(out_page);
5228c2ecf20Sopenharmony_ci		workspace->out_buf.pos = 0;
5238c2ecf20Sopenharmony_ci		workspace->out_buf.size = min_t(size_t, max_out, PAGE_SIZE);
5248c2ecf20Sopenharmony_ci	}
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_ci	if (tot_out >= tot_in) {
5278c2ecf20Sopenharmony_ci		ret = -E2BIG;
5288c2ecf20Sopenharmony_ci		goto out;
5298c2ecf20Sopenharmony_ci	}
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	ret = 0;
5328c2ecf20Sopenharmony_ci	*total_in = tot_in;
5338c2ecf20Sopenharmony_ci	*total_out = tot_out;
5348c2ecf20Sopenharmony_ciout:
5358c2ecf20Sopenharmony_ci	*out_pages = nr_pages;
5368c2ecf20Sopenharmony_ci	/* Cleanup */
5378c2ecf20Sopenharmony_ci	if (in_page) {
5388c2ecf20Sopenharmony_ci		kunmap(in_page);
5398c2ecf20Sopenharmony_ci		put_page(in_page);
5408c2ecf20Sopenharmony_ci	}
5418c2ecf20Sopenharmony_ci	if (out_page)
5428c2ecf20Sopenharmony_ci		kunmap(out_page);
5438c2ecf20Sopenharmony_ci	return ret;
5448c2ecf20Sopenharmony_ci}
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ciint zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
5478c2ecf20Sopenharmony_ci{
5488c2ecf20Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
5498c2ecf20Sopenharmony_ci	struct page **pages_in = cb->compressed_pages;
5508c2ecf20Sopenharmony_ci	u64 disk_start = cb->start;
5518c2ecf20Sopenharmony_ci	struct bio *orig_bio = cb->orig_bio;
5528c2ecf20Sopenharmony_ci	size_t srclen = cb->compressed_len;
5538c2ecf20Sopenharmony_ci	ZSTD_DStream *stream;
5548c2ecf20Sopenharmony_ci	int ret = 0;
5558c2ecf20Sopenharmony_ci	unsigned long page_in_index = 0;
5568c2ecf20Sopenharmony_ci	unsigned long total_pages_in = DIV_ROUND_UP(srclen, PAGE_SIZE);
5578c2ecf20Sopenharmony_ci	unsigned long buf_start;
5588c2ecf20Sopenharmony_ci	unsigned long total_out = 0;
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci	stream = ZSTD_initDStream(
5618c2ecf20Sopenharmony_ci			ZSTD_BTRFS_MAX_INPUT, workspace->mem, workspace->size);
5628c2ecf20Sopenharmony_ci	if (!stream) {
5638c2ecf20Sopenharmony_ci		pr_debug("BTRFS: ZSTD_initDStream failed\n");
5648c2ecf20Sopenharmony_ci		ret = -EIO;
5658c2ecf20Sopenharmony_ci		goto done;
5668c2ecf20Sopenharmony_ci	}
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_ci	workspace->in_buf.src = kmap(pages_in[page_in_index]);
5698c2ecf20Sopenharmony_ci	workspace->in_buf.pos = 0;
5708c2ecf20Sopenharmony_ci	workspace->in_buf.size = min_t(size_t, srclen, PAGE_SIZE);
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_ci	workspace->out_buf.dst = workspace->buf;
5738c2ecf20Sopenharmony_ci	workspace->out_buf.pos = 0;
5748c2ecf20Sopenharmony_ci	workspace->out_buf.size = PAGE_SIZE;
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ci	while (1) {
5778c2ecf20Sopenharmony_ci		size_t ret2;
5788c2ecf20Sopenharmony_ci
5798c2ecf20Sopenharmony_ci		ret2 = ZSTD_decompressStream(stream, &workspace->out_buf,
5808c2ecf20Sopenharmony_ci				&workspace->in_buf);
5818c2ecf20Sopenharmony_ci		if (ZSTD_isError(ret2)) {
5828c2ecf20Sopenharmony_ci			pr_debug("BTRFS: ZSTD_decompressStream returned %d\n",
5838c2ecf20Sopenharmony_ci					ZSTD_getErrorCode(ret2));
5848c2ecf20Sopenharmony_ci			ret = -EIO;
5858c2ecf20Sopenharmony_ci			goto done;
5868c2ecf20Sopenharmony_ci		}
5878c2ecf20Sopenharmony_ci		buf_start = total_out;
5888c2ecf20Sopenharmony_ci		total_out += workspace->out_buf.pos;
5898c2ecf20Sopenharmony_ci		workspace->out_buf.pos = 0;
5908c2ecf20Sopenharmony_ci
5918c2ecf20Sopenharmony_ci		ret = btrfs_decompress_buf2page(workspace->out_buf.dst,
5928c2ecf20Sopenharmony_ci				buf_start, total_out, disk_start, orig_bio);
5938c2ecf20Sopenharmony_ci		if (ret == 0)
5948c2ecf20Sopenharmony_ci			break;
5958c2ecf20Sopenharmony_ci
5968c2ecf20Sopenharmony_ci		if (workspace->in_buf.pos >= srclen)
5978c2ecf20Sopenharmony_ci			break;
5988c2ecf20Sopenharmony_ci
5998c2ecf20Sopenharmony_ci		/* Check if we've hit the end of a frame */
6008c2ecf20Sopenharmony_ci		if (ret2 == 0)
6018c2ecf20Sopenharmony_ci			break;
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_ci		if (workspace->in_buf.pos == workspace->in_buf.size) {
6048c2ecf20Sopenharmony_ci			kunmap(pages_in[page_in_index++]);
6058c2ecf20Sopenharmony_ci			if (page_in_index >= total_pages_in) {
6068c2ecf20Sopenharmony_ci				workspace->in_buf.src = NULL;
6078c2ecf20Sopenharmony_ci				ret = -EIO;
6088c2ecf20Sopenharmony_ci				goto done;
6098c2ecf20Sopenharmony_ci			}
6108c2ecf20Sopenharmony_ci			srclen -= PAGE_SIZE;
6118c2ecf20Sopenharmony_ci			workspace->in_buf.src = kmap(pages_in[page_in_index]);
6128c2ecf20Sopenharmony_ci			workspace->in_buf.pos = 0;
6138c2ecf20Sopenharmony_ci			workspace->in_buf.size = min_t(size_t, srclen, PAGE_SIZE);
6148c2ecf20Sopenharmony_ci		}
6158c2ecf20Sopenharmony_ci	}
6168c2ecf20Sopenharmony_ci	ret = 0;
6178c2ecf20Sopenharmony_ci	zero_fill_bio(orig_bio);
6188c2ecf20Sopenharmony_cidone:
6198c2ecf20Sopenharmony_ci	if (workspace->in_buf.src)
6208c2ecf20Sopenharmony_ci		kunmap(pages_in[page_in_index]);
6218c2ecf20Sopenharmony_ci	return ret;
6228c2ecf20Sopenharmony_ci}
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_ciint zstd_decompress(struct list_head *ws, unsigned char *data_in,
6258c2ecf20Sopenharmony_ci		struct page *dest_page, unsigned long start_byte, size_t srclen,
6268c2ecf20Sopenharmony_ci		size_t destlen)
6278c2ecf20Sopenharmony_ci{
6288c2ecf20Sopenharmony_ci	struct workspace *workspace = list_entry(ws, struct workspace, list);
6298c2ecf20Sopenharmony_ci	ZSTD_DStream *stream;
6308c2ecf20Sopenharmony_ci	int ret = 0;
6318c2ecf20Sopenharmony_ci	size_t ret2;
6328c2ecf20Sopenharmony_ci	unsigned long total_out = 0;
6338c2ecf20Sopenharmony_ci	unsigned long pg_offset = 0;
6348c2ecf20Sopenharmony_ci	char *kaddr;
6358c2ecf20Sopenharmony_ci
6368c2ecf20Sopenharmony_ci	stream = ZSTD_initDStream(
6378c2ecf20Sopenharmony_ci			ZSTD_BTRFS_MAX_INPUT, workspace->mem, workspace->size);
6388c2ecf20Sopenharmony_ci	if (!stream) {
6398c2ecf20Sopenharmony_ci		pr_warn("BTRFS: ZSTD_initDStream failed\n");
6408c2ecf20Sopenharmony_ci		ret = -EIO;
6418c2ecf20Sopenharmony_ci		goto finish;
6428c2ecf20Sopenharmony_ci	}
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_ci	destlen = min_t(size_t, destlen, PAGE_SIZE);
6458c2ecf20Sopenharmony_ci
6468c2ecf20Sopenharmony_ci	workspace->in_buf.src = data_in;
6478c2ecf20Sopenharmony_ci	workspace->in_buf.pos = 0;
6488c2ecf20Sopenharmony_ci	workspace->in_buf.size = srclen;
6498c2ecf20Sopenharmony_ci
6508c2ecf20Sopenharmony_ci	workspace->out_buf.dst = workspace->buf;
6518c2ecf20Sopenharmony_ci	workspace->out_buf.pos = 0;
6528c2ecf20Sopenharmony_ci	workspace->out_buf.size = PAGE_SIZE;
6538c2ecf20Sopenharmony_ci
6548c2ecf20Sopenharmony_ci	ret2 = 1;
6558c2ecf20Sopenharmony_ci	while (pg_offset < destlen
6568c2ecf20Sopenharmony_ci	       && workspace->in_buf.pos < workspace->in_buf.size) {
6578c2ecf20Sopenharmony_ci		unsigned long buf_start;
6588c2ecf20Sopenharmony_ci		unsigned long buf_offset;
6598c2ecf20Sopenharmony_ci		unsigned long bytes;
6608c2ecf20Sopenharmony_ci
6618c2ecf20Sopenharmony_ci		/* Check if the frame is over and we still need more input */
6628c2ecf20Sopenharmony_ci		if (ret2 == 0) {
6638c2ecf20Sopenharmony_ci			pr_debug("BTRFS: ZSTD_decompressStream ended early\n");
6648c2ecf20Sopenharmony_ci			ret = -EIO;
6658c2ecf20Sopenharmony_ci			goto finish;
6668c2ecf20Sopenharmony_ci		}
6678c2ecf20Sopenharmony_ci		ret2 = ZSTD_decompressStream(stream, &workspace->out_buf,
6688c2ecf20Sopenharmony_ci				&workspace->in_buf);
6698c2ecf20Sopenharmony_ci		if (ZSTD_isError(ret2)) {
6708c2ecf20Sopenharmony_ci			pr_debug("BTRFS: ZSTD_decompressStream returned %d\n",
6718c2ecf20Sopenharmony_ci					ZSTD_getErrorCode(ret2));
6728c2ecf20Sopenharmony_ci			ret = -EIO;
6738c2ecf20Sopenharmony_ci			goto finish;
6748c2ecf20Sopenharmony_ci		}
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_ci		buf_start = total_out;
6778c2ecf20Sopenharmony_ci		total_out += workspace->out_buf.pos;
6788c2ecf20Sopenharmony_ci		workspace->out_buf.pos = 0;
6798c2ecf20Sopenharmony_ci
6808c2ecf20Sopenharmony_ci		if (total_out <= start_byte)
6818c2ecf20Sopenharmony_ci			continue;
6828c2ecf20Sopenharmony_ci
6838c2ecf20Sopenharmony_ci		if (total_out > start_byte && buf_start < start_byte)
6848c2ecf20Sopenharmony_ci			buf_offset = start_byte - buf_start;
6858c2ecf20Sopenharmony_ci		else
6868c2ecf20Sopenharmony_ci			buf_offset = 0;
6878c2ecf20Sopenharmony_ci
6888c2ecf20Sopenharmony_ci		bytes = min_t(unsigned long, destlen - pg_offset,
6898c2ecf20Sopenharmony_ci				workspace->out_buf.size - buf_offset);
6908c2ecf20Sopenharmony_ci
6918c2ecf20Sopenharmony_ci		kaddr = kmap_atomic(dest_page);
6928c2ecf20Sopenharmony_ci		memcpy(kaddr + pg_offset, workspace->out_buf.dst + buf_offset,
6938c2ecf20Sopenharmony_ci				bytes);
6948c2ecf20Sopenharmony_ci		kunmap_atomic(kaddr);
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci		pg_offset += bytes;
6978c2ecf20Sopenharmony_ci	}
6988c2ecf20Sopenharmony_ci	ret = 0;
6998c2ecf20Sopenharmony_cifinish:
7008c2ecf20Sopenharmony_ci	if (pg_offset < destlen) {
7018c2ecf20Sopenharmony_ci		kaddr = kmap_atomic(dest_page);
7028c2ecf20Sopenharmony_ci		memset(kaddr + pg_offset, 0, destlen - pg_offset);
7038c2ecf20Sopenharmony_ci		kunmap_atomic(kaddr);
7048c2ecf20Sopenharmony_ci	}
7058c2ecf20Sopenharmony_ci	return ret;
7068c2ecf20Sopenharmony_ci}
7078c2ecf20Sopenharmony_ci
7088c2ecf20Sopenharmony_ciconst struct btrfs_compress_op btrfs_zstd_compress = {
7098c2ecf20Sopenharmony_ci	/* ZSTD uses own workspace manager */
7108c2ecf20Sopenharmony_ci	.workspace_manager = NULL,
7118c2ecf20Sopenharmony_ci	.max_level	= ZSTD_BTRFS_MAX_LEVEL,
7128c2ecf20Sopenharmony_ci	.default_level	= ZSTD_BTRFS_DEFAULT_LEVEL,
7138c2ecf20Sopenharmony_ci};
714