| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (c) 2013 | |
| * Minchan Kim <minchan@kernel.org> | |
| */ | |
| #include <linux/types.h> | |
| #include <linux/mutex.h> | |
| #include <linux/slab.h> | |
| #include <linux/bio.h> | |
| #include <linux/sched.h> | |
| #include <linux/wait.h> | |
| #include <linux/cpumask.h> | |
| #include "squashfs_fs.h" | |
| #include "squashfs_fs_sb.h" | |
| #include "decompressor.h" | |
| #include "squashfs.h" | |
| /* | |
| * This file implements multi-threaded decompression in the | |
| * decompressor framework | |
| */ | |
| /* | |
| * The reason that multiply two is that a CPU can request new I/O | |
| * while it is waiting previous request. | |
| */ | |
| #define MAX_DECOMPRESSOR (num_online_cpus() * 2) | |
| static int squashfs_max_decompressors(void) | |
| { | |
| return MAX_DECOMPRESSOR; | |
| } | |
| struct squashfs_stream { | |
| void *comp_opts; | |
| struct list_head strm_list; | |
| struct mutex mutex; | |
| int avail_decomp; | |
| wait_queue_head_t wait; | |
| }; | |
| struct decomp_stream { | |
| void *stream; | |
| struct list_head list; | |
| }; | |
| static void put_decomp_stream(struct decomp_stream *decomp_strm, | |
| struct squashfs_stream *stream) | |
| { | |
| mutex_lock(&stream->mutex); | |
| list_add(&decomp_strm->list, &stream->strm_list); | |
| mutex_unlock(&stream->mutex); | |
| wake_up(&stream->wait); | |
| } | |
| static void *squashfs_decompressor_create(struct squashfs_sb_info *msblk, | |
| void *comp_opts) | |
| { | |
| struct squashfs_stream *stream; | |
| struct decomp_stream *decomp_strm = NULL; | |
| int err = -ENOMEM; | |
| stream = kzalloc_obj(*stream); | |
| if (!stream) | |
| goto out; | |
| stream->comp_opts = comp_opts; | |
| mutex_init(&stream->mutex); | |
| INIT_LIST_HEAD(&stream->strm_list); | |
| init_waitqueue_head(&stream->wait); | |
| /* | |
| * We should have a decompressor at least as default | |
| * so if we fail to allocate new decompressor dynamically, | |
| * we could always fall back to default decompressor and | |
| * file system works. | |
| */ | |
| decomp_strm = kmalloc_obj(*decomp_strm); | |
| if (!decomp_strm) | |
| goto out; | |
| decomp_strm->stream = msblk->decompressor->init(msblk, | |
| stream->comp_opts); | |
| if (IS_ERR(decomp_strm->stream)) { | |
| err = PTR_ERR(decomp_strm->stream); | |
| goto out; | |
| } | |
| list_add(&decomp_strm->list, &stream->strm_list); | |
| stream->avail_decomp = 1; | |
| return stream; | |
| out: | |
| kfree(decomp_strm); | |
| kfree(stream); | |
| return ERR_PTR(err); | |
| } | |
| static void squashfs_decompressor_destroy(struct squashfs_sb_info *msblk) | |
| { | |
| struct squashfs_stream *stream = msblk->stream; | |
| if (stream) { | |
| struct decomp_stream *decomp_strm; | |
| while (!list_empty(&stream->strm_list)) { | |
| decomp_strm = list_entry(stream->strm_list.prev, | |
| struct decomp_stream, list); | |
| list_del(&decomp_strm->list); | |
| msblk->decompressor->free(decomp_strm->stream); | |
| kfree(decomp_strm); | |
| stream->avail_decomp--; | |
| } | |
| WARN_ON(stream->avail_decomp); | |
| kfree(stream->comp_opts); | |
| kfree(stream); | |
| } | |
| } | |
| static struct decomp_stream *get_decomp_stream(struct squashfs_sb_info *msblk, | |
| struct squashfs_stream *stream) | |
| { | |
| struct decomp_stream *decomp_strm; | |
| while (1) { | |
| mutex_lock(&stream->mutex); | |
| /* There is available decomp_stream */ | |
| if (!list_empty(&stream->strm_list)) { | |
| decomp_strm = list_entry(stream->strm_list.prev, | |
| struct decomp_stream, list); | |
| list_del(&decomp_strm->list); | |
| mutex_unlock(&stream->mutex); | |
| break; | |
| } | |
| /* | |
| * If there is no available decomp and already full, | |
| * let's wait for releasing decomp from other users. | |
| */ | |
| if (stream->avail_decomp >= msblk->max_thread_num) | |
| goto wait; | |
| /* Let's allocate new decomp */ | |
| decomp_strm = kmalloc_obj(*decomp_strm); | |
| if (!decomp_strm) | |
| goto wait; | |
| decomp_strm->stream = msblk->decompressor->init(msblk, | |
| stream->comp_opts); | |
| if (IS_ERR(decomp_strm->stream)) { | |
| kfree(decomp_strm); | |
| goto wait; | |
| } | |
| stream->avail_decomp++; | |
| WARN_ON(stream->avail_decomp > msblk->max_thread_num); | |
| mutex_unlock(&stream->mutex); | |
| break; | |
| wait: | |
| /* | |
| * If system memory is tough, let's for other's | |
| * releasing instead of hurting VM because it could | |
| * make page cache thrashing. | |
| */ | |
| mutex_unlock(&stream->mutex); | |
| wait_event(stream->wait, | |
| !list_empty(&stream->strm_list)); | |
| } | |
| return decomp_strm; | |
| } | |
| static int squashfs_decompress(struct squashfs_sb_info *msblk, struct bio *bio, | |
| int offset, int length, | |
| struct squashfs_page_actor *output) | |
| { | |
| int res; | |
| struct squashfs_stream *stream = msblk->stream; | |
| struct decomp_stream *decomp_stream = get_decomp_stream(msblk, stream); | |
| res = msblk->decompressor->decompress(msblk, decomp_stream->stream, | |
| bio, offset, length, output); | |
| put_decomp_stream(decomp_stream, stream); | |
| if (res < 0) | |
| ERROR("%s decompression failed, data probably corrupt\n", | |
| msblk->decompressor->name); | |
| return res; | |
| } | |
| const struct squashfs_decompressor_thread_ops squashfs_decompressor_multi = { | |
| .create = squashfs_decompressor_create, | |
| .destroy = squashfs_decompressor_destroy, | |
| .decompress = squashfs_decompress, | |
| .max_decompressors = squashfs_max_decompressors, | |
| }; |