18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Copyright (c) 2013
48c2ecf20Sopenharmony_ci *  Minchan Kim <minchan@kernel.org>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci#include <linux/types.h>
78c2ecf20Sopenharmony_ci#include <linux/mutex.h>
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci#include <linux/bio.h>
108c2ecf20Sopenharmony_ci#include <linux/sched.h>
118c2ecf20Sopenharmony_ci#include <linux/wait.h>
128c2ecf20Sopenharmony_ci#include <linux/cpumask.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include "squashfs_fs.h"
158c2ecf20Sopenharmony_ci#include "squashfs_fs_sb.h"
168c2ecf20Sopenharmony_ci#include "decompressor.h"
178c2ecf20Sopenharmony_ci#include "squashfs.h"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci/*
208c2ecf20Sopenharmony_ci * This file implements multi-threaded decompression in the
218c2ecf20Sopenharmony_ci * decompressor framework
228c2ecf20Sopenharmony_ci */
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/*
268c2ecf20Sopenharmony_ci * The reason that multiply two is that a CPU can request new I/O
278c2ecf20Sopenharmony_ci * while it is waiting previous request.
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_ci#define MAX_DECOMPRESSOR	(num_online_cpus() * 2)
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ciint squashfs_max_decompressors(void)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	return MAX_DECOMPRESSOR;
358c2ecf20Sopenharmony_ci}
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistruct squashfs_stream {
398c2ecf20Sopenharmony_ci	void			*comp_opts;
408c2ecf20Sopenharmony_ci	struct list_head	strm_list;
418c2ecf20Sopenharmony_ci	struct mutex		mutex;
428c2ecf20Sopenharmony_ci	int			avail_decomp;
438c2ecf20Sopenharmony_ci	wait_queue_head_t	wait;
448c2ecf20Sopenharmony_ci};
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistruct decomp_stream {
488c2ecf20Sopenharmony_ci	void *stream;
498c2ecf20Sopenharmony_ci	struct list_head list;
508c2ecf20Sopenharmony_ci};
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic void put_decomp_stream(struct decomp_stream *decomp_strm,
548c2ecf20Sopenharmony_ci				struct squashfs_stream *stream)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	mutex_lock(&stream->mutex);
578c2ecf20Sopenharmony_ci	list_add(&decomp_strm->list, &stream->strm_list);
588c2ecf20Sopenharmony_ci	mutex_unlock(&stream->mutex);
598c2ecf20Sopenharmony_ci	wake_up(&stream->wait);
608c2ecf20Sopenharmony_ci}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_civoid *squashfs_decompressor_create(struct squashfs_sb_info *msblk,
638c2ecf20Sopenharmony_ci				void *comp_opts)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	struct squashfs_stream *stream;
668c2ecf20Sopenharmony_ci	struct decomp_stream *decomp_strm = NULL;
678c2ecf20Sopenharmony_ci	int err = -ENOMEM;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	stream = kzalloc(sizeof(*stream), GFP_KERNEL);
708c2ecf20Sopenharmony_ci	if (!stream)
718c2ecf20Sopenharmony_ci		goto out;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	stream->comp_opts = comp_opts;
748c2ecf20Sopenharmony_ci	mutex_init(&stream->mutex);
758c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&stream->strm_list);
768c2ecf20Sopenharmony_ci	init_waitqueue_head(&stream->wait);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	/*
798c2ecf20Sopenharmony_ci	 * We should have a decompressor at least as default
808c2ecf20Sopenharmony_ci	 * so if we fail to allocate new decompressor dynamically,
818c2ecf20Sopenharmony_ci	 * we could always fall back to default decompressor and
828c2ecf20Sopenharmony_ci	 * file system works.
838c2ecf20Sopenharmony_ci	 */
848c2ecf20Sopenharmony_ci	decomp_strm = kmalloc(sizeof(*decomp_strm), GFP_KERNEL);
858c2ecf20Sopenharmony_ci	if (!decomp_strm)
868c2ecf20Sopenharmony_ci		goto out;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	decomp_strm->stream = msblk->decompressor->init(msblk,
898c2ecf20Sopenharmony_ci						stream->comp_opts);
908c2ecf20Sopenharmony_ci	if (IS_ERR(decomp_strm->stream)) {
918c2ecf20Sopenharmony_ci		err = PTR_ERR(decomp_strm->stream);
928c2ecf20Sopenharmony_ci		goto out;
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	list_add(&decomp_strm->list, &stream->strm_list);
968c2ecf20Sopenharmony_ci	stream->avail_decomp = 1;
978c2ecf20Sopenharmony_ci	return stream;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ciout:
1008c2ecf20Sopenharmony_ci	kfree(decomp_strm);
1018c2ecf20Sopenharmony_ci	kfree(stream);
1028c2ecf20Sopenharmony_ci	return ERR_PTR(err);
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_civoid squashfs_decompressor_destroy(struct squashfs_sb_info *msblk)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	struct squashfs_stream *stream = msblk->stream;
1098c2ecf20Sopenharmony_ci	if (stream) {
1108c2ecf20Sopenharmony_ci		struct decomp_stream *decomp_strm;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci		while (!list_empty(&stream->strm_list)) {
1138c2ecf20Sopenharmony_ci			decomp_strm = list_entry(stream->strm_list.prev,
1148c2ecf20Sopenharmony_ci						struct decomp_stream, list);
1158c2ecf20Sopenharmony_ci			list_del(&decomp_strm->list);
1168c2ecf20Sopenharmony_ci			msblk->decompressor->free(decomp_strm->stream);
1178c2ecf20Sopenharmony_ci			kfree(decomp_strm);
1188c2ecf20Sopenharmony_ci			stream->avail_decomp--;
1198c2ecf20Sopenharmony_ci		}
1208c2ecf20Sopenharmony_ci		WARN_ON(stream->avail_decomp);
1218c2ecf20Sopenharmony_ci		kfree(stream->comp_opts);
1228c2ecf20Sopenharmony_ci		kfree(stream);
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistatic struct decomp_stream *get_decomp_stream(struct squashfs_sb_info *msblk,
1288c2ecf20Sopenharmony_ci					struct squashfs_stream *stream)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	struct decomp_stream *decomp_strm;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	while (1) {
1338c2ecf20Sopenharmony_ci		mutex_lock(&stream->mutex);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci		/* There is available decomp_stream */
1368c2ecf20Sopenharmony_ci		if (!list_empty(&stream->strm_list)) {
1378c2ecf20Sopenharmony_ci			decomp_strm = list_entry(stream->strm_list.prev,
1388c2ecf20Sopenharmony_ci				struct decomp_stream, list);
1398c2ecf20Sopenharmony_ci			list_del(&decomp_strm->list);
1408c2ecf20Sopenharmony_ci			mutex_unlock(&stream->mutex);
1418c2ecf20Sopenharmony_ci			break;
1428c2ecf20Sopenharmony_ci		}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci		/*
1458c2ecf20Sopenharmony_ci		 * If there is no available decomp and already full,
1468c2ecf20Sopenharmony_ci		 * let's wait for releasing decomp from other users.
1478c2ecf20Sopenharmony_ci		 */
1488c2ecf20Sopenharmony_ci		if (stream->avail_decomp >= MAX_DECOMPRESSOR)
1498c2ecf20Sopenharmony_ci			goto wait;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci		/* Let's allocate new decomp */
1528c2ecf20Sopenharmony_ci		decomp_strm = kmalloc(sizeof(*decomp_strm), GFP_KERNEL);
1538c2ecf20Sopenharmony_ci		if (!decomp_strm)
1548c2ecf20Sopenharmony_ci			goto wait;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci		decomp_strm->stream = msblk->decompressor->init(msblk,
1578c2ecf20Sopenharmony_ci						stream->comp_opts);
1588c2ecf20Sopenharmony_ci		if (IS_ERR(decomp_strm->stream)) {
1598c2ecf20Sopenharmony_ci			kfree(decomp_strm);
1608c2ecf20Sopenharmony_ci			goto wait;
1618c2ecf20Sopenharmony_ci		}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci		stream->avail_decomp++;
1648c2ecf20Sopenharmony_ci		WARN_ON(stream->avail_decomp > MAX_DECOMPRESSOR);
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci		mutex_unlock(&stream->mutex);
1678c2ecf20Sopenharmony_ci		break;
1688c2ecf20Sopenharmony_ciwait:
1698c2ecf20Sopenharmony_ci		/*
1708c2ecf20Sopenharmony_ci		 * If system memory is tough, let's for other's
1718c2ecf20Sopenharmony_ci		 * releasing instead of hurting VM because it could
1728c2ecf20Sopenharmony_ci		 * make page cache thrashing.
1738c2ecf20Sopenharmony_ci		 */
1748c2ecf20Sopenharmony_ci		mutex_unlock(&stream->mutex);
1758c2ecf20Sopenharmony_ci		wait_event(stream->wait,
1768c2ecf20Sopenharmony_ci			!list_empty(&stream->strm_list));
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	return decomp_strm;
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ciint squashfs_decompress(struct squashfs_sb_info *msblk, struct bio *bio,
1848c2ecf20Sopenharmony_ci			int offset, int length,
1858c2ecf20Sopenharmony_ci			struct squashfs_page_actor *output)
1868c2ecf20Sopenharmony_ci{
1878c2ecf20Sopenharmony_ci	int res;
1888c2ecf20Sopenharmony_ci	struct squashfs_stream *stream = msblk->stream;
1898c2ecf20Sopenharmony_ci	struct decomp_stream *decomp_stream = get_decomp_stream(msblk, stream);
1908c2ecf20Sopenharmony_ci	res = msblk->decompressor->decompress(msblk, decomp_stream->stream,
1918c2ecf20Sopenharmony_ci		bio, offset, length, output);
1928c2ecf20Sopenharmony_ci	put_decomp_stream(decomp_stream, stream);
1938c2ecf20Sopenharmony_ci	if (res < 0)
1948c2ecf20Sopenharmony_ci		ERROR("%s decompression failed, data probably corrupt\n",
1958c2ecf20Sopenharmony_ci			msblk->decompressor->name);
1968c2ecf20Sopenharmony_ci	return res;
1978c2ecf20Sopenharmony_ci}
198