1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 *  Copyright (c) 2013
4 *  Minchan Kim <minchan@kernel.org>
5 */
6#include <linux/types.h>
7#include <linux/mutex.h>
8#include <linux/slab.h>
9#include <linux/bio.h>
10#include <linux/sched.h>
11#include <linux/wait.h>
12#include <linux/cpumask.h>
13
14#include "squashfs_fs.h"
15#include "squashfs_fs_sb.h"
16#include "decompressor.h"
17#include "squashfs.h"
18
19/*
20 * This file implements multi-threaded decompression in the
21 * decompressor framework
22 */
23
24
25/*
26 * The reason that multiply two is that a CPU can request new I/O
27 * while it is waiting previous request.
28 */
29#define MAX_DECOMPRESSOR	(num_online_cpus() * 2)
30
31
32int squashfs_max_decompressors(void)
33{
34	return MAX_DECOMPRESSOR;
35}
36
37
38struct squashfs_stream {
39	void			*comp_opts;
40	struct list_head	strm_list;
41	struct mutex		mutex;
42	int			avail_decomp;
43	wait_queue_head_t	wait;
44};
45
46
47struct decomp_stream {
48	void *stream;
49	struct list_head list;
50};
51
52
53static void put_decomp_stream(struct decomp_stream *decomp_strm,
54				struct squashfs_stream *stream)
55{
56	mutex_lock(&stream->mutex);
57	list_add(&decomp_strm->list, &stream->strm_list);
58	mutex_unlock(&stream->mutex);
59	wake_up(&stream->wait);
60}
61
62void *squashfs_decompressor_create(struct squashfs_sb_info *msblk,
63				void *comp_opts)
64{
65	struct squashfs_stream *stream;
66	struct decomp_stream *decomp_strm = NULL;
67	int err = -ENOMEM;
68
69	stream = kzalloc(sizeof(*stream), GFP_KERNEL);
70	if (!stream)
71		goto out;
72
73	stream->comp_opts = comp_opts;
74	mutex_init(&stream->mutex);
75	INIT_LIST_HEAD(&stream->strm_list);
76	init_waitqueue_head(&stream->wait);
77
78	/*
79	 * We should have a decompressor at least as default
80	 * so if we fail to allocate new decompressor dynamically,
81	 * we could always fall back to default decompressor and
82	 * file system works.
83	 */
84	decomp_strm = kmalloc(sizeof(*decomp_strm), GFP_KERNEL);
85	if (!decomp_strm)
86		goto out;
87
88	decomp_strm->stream = msblk->decompressor->init(msblk,
89						stream->comp_opts);
90	if (IS_ERR(decomp_strm->stream)) {
91		err = PTR_ERR(decomp_strm->stream);
92		goto out;
93	}
94
95	list_add(&decomp_strm->list, &stream->strm_list);
96	stream->avail_decomp = 1;
97	return stream;
98
99out:
100	kfree(decomp_strm);
101	kfree(stream);
102	return ERR_PTR(err);
103}
104
105
106void squashfs_decompressor_destroy(struct squashfs_sb_info *msblk)
107{
108	struct squashfs_stream *stream = msblk->stream;
109	if (stream) {
110		struct decomp_stream *decomp_strm;
111
112		while (!list_empty(&stream->strm_list)) {
113			decomp_strm = list_entry(stream->strm_list.prev,
114						struct decomp_stream, list);
115			list_del(&decomp_strm->list);
116			msblk->decompressor->free(decomp_strm->stream);
117			kfree(decomp_strm);
118			stream->avail_decomp--;
119		}
120		WARN_ON(stream->avail_decomp);
121		kfree(stream->comp_opts);
122		kfree(stream);
123	}
124}
125
126
127static struct decomp_stream *get_decomp_stream(struct squashfs_sb_info *msblk,
128					struct squashfs_stream *stream)
129{
130	struct decomp_stream *decomp_strm;
131
132	while (1) {
133		mutex_lock(&stream->mutex);
134
135		/* There is available decomp_stream */
136		if (!list_empty(&stream->strm_list)) {
137			decomp_strm = list_entry(stream->strm_list.prev,
138				struct decomp_stream, list);
139			list_del(&decomp_strm->list);
140			mutex_unlock(&stream->mutex);
141			break;
142		}
143
144		/*
145		 * If there is no available decomp and already full,
146		 * let's wait for releasing decomp from other users.
147		 */
148		if (stream->avail_decomp >= MAX_DECOMPRESSOR)
149			goto wait;
150
151		/* Let's allocate new decomp */
152		decomp_strm = kmalloc(sizeof(*decomp_strm), GFP_KERNEL);
153		if (!decomp_strm)
154			goto wait;
155
156		decomp_strm->stream = msblk->decompressor->init(msblk,
157						stream->comp_opts);
158		if (IS_ERR(decomp_strm->stream)) {
159			kfree(decomp_strm);
160			goto wait;
161		}
162
163		stream->avail_decomp++;
164		WARN_ON(stream->avail_decomp > MAX_DECOMPRESSOR);
165
166		mutex_unlock(&stream->mutex);
167		break;
168wait:
169		/*
170		 * If system memory is tough, let's for other's
171		 * releasing instead of hurting VM because it could
172		 * make page cache thrashing.
173		 */
174		mutex_unlock(&stream->mutex);
175		wait_event(stream->wait,
176			!list_empty(&stream->strm_list));
177	}
178
179	return decomp_strm;
180}
181
182
183int squashfs_decompress(struct squashfs_sb_info *msblk, struct bio *bio,
184			int offset, int length,
185			struct squashfs_page_actor *output)
186{
187	int res;
188	struct squashfs_stream *stream = msblk->stream;
189	struct decomp_stream *decomp_stream = get_decomp_stream(msblk, stream);
190	res = msblk->decompressor->decompress(msblk, decomp_stream->stream,
191		bio, offset, length, output);
192	put_decomp_stream(decomp_stream, stream);
193	if (res < 0)
194		ERROR("%s decompression failed, data probably corrupt\n",
195			msblk->decompressor->name);
196	return res;
197}
198