// 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) 
 | 
  
 | 
  
 | 
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 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; 
 | 
} 
 |