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 32static int squashfs_max_decompressors(void) 33{ 34 return MAX_DECOMPRESSOR; 35} 36 37struct squashfs_stream { 38 void *comp_opts; 39 struct list_head strm_list; 40 struct mutex mutex; 41 int avail_decomp; 42 wait_queue_head_t wait; 43}; 44 45 46struct decomp_stream { 47 void *stream; 48 struct list_head list; 49}; 50 51 52static void put_decomp_stream(struct decomp_stream *decomp_strm, 53 struct squashfs_stream *stream) 54{ 55 mutex_lock(&stream->mutex); 56 list_add(&decomp_strm->list, &stream->strm_list); 57 mutex_unlock(&stream->mutex); 58 wake_up(&stream->wait); 59} 60 61static void *squashfs_decompressor_create(struct squashfs_sb_info *msblk, 62 void *comp_opts) 63{ 64 struct squashfs_stream *stream; 65 struct decomp_stream *decomp_strm = NULL; 66 int err = -ENOMEM; 67 68 stream = kzalloc(sizeof(*stream), GFP_KERNEL); 69 if (!stream) 70 goto out; 71 72 stream->comp_opts = comp_opts; 73 mutex_init(&stream->mutex); 74 INIT_LIST_HEAD(&stream->strm_list); 75 init_waitqueue_head(&stream->wait); 76 77 /* 78 * We should have a decompressor at least as default 79 * so if we fail to allocate new decompressor dynamically, 80 * we could always fall back to default decompressor and 81 * file system works. 82 */ 83 decomp_strm = kmalloc(sizeof(*decomp_strm), GFP_KERNEL); 84 if (!decomp_strm) 85 goto out; 86 87 decomp_strm->stream = msblk->decompressor->init(msblk, 88 stream->comp_opts); 89 if (IS_ERR(decomp_strm->stream)) { 90 err = PTR_ERR(decomp_strm->stream); 91 goto out; 92 } 93 94 list_add(&decomp_strm->list, &stream->strm_list); 95 stream->avail_decomp = 1; 96 return stream; 97 98out: 99 kfree(decomp_strm); 100 kfree(stream); 101 return ERR_PTR(err); 102} 103 104 105static void squashfs_decompressor_destroy(struct squashfs_sb_info *msblk) 106{ 107 struct squashfs_stream *stream = msblk->stream; 108 if (stream) { 109 struct decomp_stream *decomp_strm; 110 111 while (!list_empty(&stream->strm_list)) { 112 decomp_strm = list_entry(stream->strm_list.prev, 113 struct decomp_stream, list); 114 list_del(&decomp_strm->list); 115 msblk->decompressor->free(decomp_strm->stream); 116 kfree(decomp_strm); 117 stream->avail_decomp--; 118 } 119 WARN_ON(stream->avail_decomp); 120 kfree(stream->comp_opts); 121 kfree(stream); 122 } 123} 124 125 126static struct decomp_stream *get_decomp_stream(struct squashfs_sb_info *msblk, 127 struct squashfs_stream *stream) 128{ 129 struct decomp_stream *decomp_strm; 130 131 while (1) { 132 mutex_lock(&stream->mutex); 133 134 /* There is available decomp_stream */ 135 if (!list_empty(&stream->strm_list)) { 136 decomp_strm = list_entry(stream->strm_list.prev, 137 struct decomp_stream, list); 138 list_del(&decomp_strm->list); 139 mutex_unlock(&stream->mutex); 140 break; 141 } 142 143 /* 144 * If there is no available decomp and already full, 145 * let's wait for releasing decomp from other users. 146 */ 147 if (stream->avail_decomp >= msblk->max_thread_num) 148 goto wait; 149 150 /* Let's allocate new decomp */ 151 decomp_strm = kmalloc(sizeof(*decomp_strm), GFP_KERNEL); 152 if (!decomp_strm) 153 goto wait; 154 155 decomp_strm->stream = msblk->decompressor->init(msblk, 156 stream->comp_opts); 157 if (IS_ERR(decomp_strm->stream)) { 158 kfree(decomp_strm); 159 goto wait; 160 } 161 162 stream->avail_decomp++; 163 WARN_ON(stream->avail_decomp > msblk->max_thread_num); 164 165 mutex_unlock(&stream->mutex); 166 break; 167wait: 168 /* 169 * If system memory is tough, let's for other's 170 * releasing instead of hurting VM because it could 171 * make page cache thrashing. 172 */ 173 mutex_unlock(&stream->mutex); 174 wait_event(stream->wait, 175 !list_empty(&stream->strm_list)); 176 } 177 178 return decomp_strm; 179} 180 181 182static int squashfs_decompress(struct squashfs_sb_info *msblk, struct bio *bio, 183 int offset, int length, 184 struct squashfs_page_actor *output) 185{ 186 int res; 187 struct squashfs_stream *stream = msblk->stream; 188 struct decomp_stream *decomp_stream = get_decomp_stream(msblk, stream); 189 res = msblk->decompressor->decompress(msblk, decomp_stream->stream, 190 bio, offset, length, output); 191 put_decomp_stream(decomp_stream, stream); 192 if (res < 0) 193 ERROR("%s decompression failed, data probably corrupt\n", 194 msblk->decompressor->name); 195 return res; 196} 197 198const struct squashfs_decompressor_thread_ops squashfs_decompressor_multi = { 199 .create = squashfs_decompressor_create, 200 .destroy = squashfs_decompressor_destroy, 201 .decompress = squashfs_decompress, 202 .max_decompressors = squashfs_max_decompressors, 203}; 204