| /* | |
| * Copyright (c) 2013 | |
| * Minchan Kim <minchan@kernel.org> | |
| * | |
| * This work is licensed under the terms of the GNU GPL, version 2. See | |
| * the COPYING file in the top-level directory. | |
| */ | |
| #include <linux/types.h> | |
| #include <linux/mutex.h> | |
| #include <linux/slab.h> | |
| #include <linux/buffer_head.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) | |
| 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); | |
| } | |
| 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(sizeof(*stream), GFP_KERNEL); | |
| 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(sizeof(*decomp_strm), GFP_KERNEL); | |
| 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); | |
| } | |
| 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 >= MAX_DECOMPRESSOR) | |
| goto wait; | |
| /* Let's allocate new decomp */ | |
| decomp_strm = kmalloc(sizeof(*decomp_strm), GFP_KERNEL); | |
| 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 > MAX_DECOMPRESSOR); | |
| 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; | |
| } | |
| int squashfs_decompress(struct squashfs_sb_info *msblk, struct buffer_head **bh, | |
| int b, 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, | |
| bh, b, 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; | |
| } |