// SPDX-License-Identifier: GPL-2.0-or-later #include #include "compress.h" struct z_erofs_lzma { struct z_erofs_lzma *next; struct xz_dec_microlzma *state; u8 bounce[PAGE_SIZE]; }; /* considering the LZMA performance, no need to use a lockless list for now */ static DEFINE_SPINLOCK(z_erofs_lzma_lock); static unsigned int z_erofs_lzma_max_dictsize; static unsigned int z_erofs_lzma_nstrms, z_erofs_lzma_avail_strms; static struct z_erofs_lzma *z_erofs_lzma_head; static DECLARE_WAIT_QUEUE_HEAD(z_erofs_lzma_wq); module_param_named(lzma_streams, z_erofs_lzma_nstrms, uint, 0444); static void z_erofs_lzma_exit(void) { /* there should be no running fs instance */ while (z_erofs_lzma_avail_strms) { struct z_erofs_lzma *strm; spin_lock(&z_erofs_lzma_lock); strm = z_erofs_lzma_head; if (!strm) { spin_unlock(&z_erofs_lzma_lock); DBG_BUGON(1); return; } z_erofs_lzma_head = NULL; spin_unlock(&z_erofs_lzma_lock); while (strm) { struct z_erofs_lzma *n = strm->next; if (strm->state) xz_dec_microlzma_end(strm->state); kfree(strm); --z_erofs_lzma_avail_strms; strm = n; } } } static int __init z_erofs_lzma_init(void) { unsigned int i; /* by default, use # of possible CPUs instead */ if (!z_erofs_lzma_nstrms) z_erofs_lzma_nstrms = num_possible_cpus(); for (i = 0; i < z_erofs_lzma_nstrms; ++i) { struct z_erofs_lzma *strm = kzalloc(sizeof(*strm), GFP_KERNEL); if (!strm) { z_erofs_lzma_exit(); return -ENOMEM; } spin_lock(&z_erofs_lzma_lock); strm->next = z_erofs_lzma_head; z_erofs_lzma_head = strm; spin_unlock(&z_erofs_lzma_lock); ++z_erofs_lzma_avail_strms; } return 0; } static int z_erofs_load_lzma_config(struct super_block *sb, struct erofs_super_block *dsb, void *data, int size) { static DEFINE_MUTEX(lzma_resize_mutex); struct z_erofs_lzma_cfgs *lzma = data; unsigned int dict_size, i; struct z_erofs_lzma *strm, *head = NULL; int err; if (!lzma || size < sizeof(struct z_erofs_lzma_cfgs)) { erofs_err(sb, "invalid lzma cfgs, size=%u", size); return -EINVAL; } if (lzma->format) { erofs_err(sb, "unidentified lzma format %x, please check kernel version", le16_to_cpu(lzma->format)); return -EINVAL; } dict_size = le32_to_cpu(lzma->dict_size); if (dict_size > Z_EROFS_LZMA_MAX_DICT_SIZE || dict_size < 4096) { erofs_err(sb, "unsupported lzma dictionary size %u", dict_size); return -EINVAL; } /* in case 2 z_erofs_load_lzma_config() race to avoid deadlock */ mutex_lock(&lzma_resize_mutex); if (z_erofs_lzma_max_dictsize >= dict_size) { mutex_unlock(&lzma_resize_mutex); return 0; } /* 1. collect/isolate all streams for the following check */ for (i = 0; i < z_erofs_lzma_avail_strms; ++i) { struct z_erofs_lzma *last; again: spin_lock(&z_erofs_lzma_lock); strm = z_erofs_lzma_head; if (!strm) { spin_unlock(&z_erofs_lzma_lock); wait_event(z_erofs_lzma_wq, READ_ONCE(z_erofs_lzma_head)); goto again; } z_erofs_lzma_head = NULL; spin_unlock(&z_erofs_lzma_lock); for (last = strm; last->next; last = last->next) ++i; last->next = head; head = strm; } err = 0; /* 2. walk each isolated stream and grow max dict_size if needed */ for (strm = head; strm; strm = strm->next) { if (strm->state) xz_dec_microlzma_end(strm->state); strm->state = xz_dec_microlzma_alloc(XZ_PREALLOC, dict_size); if (!strm->state) err = -ENOMEM; } /* 3. push back all to the global list and update max dict_size */ spin_lock(&z_erofs_lzma_lock); DBG_BUGON(z_erofs_lzma_head); z_erofs_lzma_head = head; spin_unlock(&z_erofs_lzma_lock); wake_up_all(&z_erofs_lzma_wq); z_erofs_lzma_max_dictsize = dict_size; mutex_unlock(&lzma_resize_mutex); return err; } static int z_erofs_lzma_decompress(struct z_erofs_decompress_req *rq, struct page **pgpl) { struct super_block *sb = rq->sb; struct z_erofs_stream_dctx dctx = { .rq = rq, .inpages = PAGE_ALIGN(rq->inputsize) >> PAGE_SHIFT, .outpages = PAGE_ALIGN(rq->pageofs_out + rq->outputsize) >> PAGE_SHIFT, .no = -1, .ni = 0, }; struct xz_buf buf = {}; struct z_erofs_lzma *strm; enum xz_ret xz_err; int err; /* 1. get the exact LZMA compressed size */ dctx.kin = kmap_local_page(*rq->in); err = z_erofs_fixup_insize(rq, dctx.kin + rq->pageofs_in, min(rq->inputsize, sb->s_blocksize - rq->pageofs_in)); if (err) { kunmap_local(dctx.kin); return err; } /* 2. get an available lzma context */ again: spin_lock(&z_erofs_lzma_lock); strm = z_erofs_lzma_head; if (!strm) { spin_unlock(&z_erofs_lzma_lock); wait_event(z_erofs_lzma_wq, READ_ONCE(z_erofs_lzma_head)); goto again; } z_erofs_lzma_head = strm->next; spin_unlock(&z_erofs_lzma_lock); /* 3. multi-call decompress */ xz_dec_microlzma_reset(strm->state, rq->inputsize, rq->outputsize, !rq->partial_decoding); buf.in_size = min(rq->inputsize, PAGE_SIZE - rq->pageofs_in); rq->inputsize -= buf.in_size; buf.in = dctx.kin + rq->pageofs_in, dctx.bounce = strm->bounce; do { dctx.avail_out = buf.out_size - buf.out_pos; dctx.inbuf_sz = buf.in_size; dctx.inbuf_pos = buf.in_pos; err = z_erofs_stream_switch_bufs(&dctx, (void **)&buf.out, (void **)&buf.in, pgpl); if (err) break; if (buf.out_size == buf.out_pos) { buf.out_size = dctx.avail_out; buf.out_pos = 0; } buf.in_size = dctx.inbuf_sz; buf.in_pos = dctx.inbuf_pos; xz_err = xz_dec_microlzma_run(strm->state, &buf); DBG_BUGON(buf.out_pos > buf.out_size); DBG_BUGON(buf.in_pos > buf.in_size); if (xz_err != XZ_OK) { if (xz_err == XZ_STREAM_END && !rq->outputsize) break; erofs_err(sb, "failed to decompress %d in[%u] out[%u]", xz_err, rq->inputsize, rq->outputsize); err = -EFSCORRUPTED; break; } } while (1); if (dctx.kout) kunmap_local(dctx.kout); kunmap_local(dctx.kin); /* 4. push back LZMA stream context to the global list */ spin_lock(&z_erofs_lzma_lock); strm->next = z_erofs_lzma_head; z_erofs_lzma_head = strm; spin_unlock(&z_erofs_lzma_lock); wake_up(&z_erofs_lzma_wq); return err; } const struct z_erofs_decompressor z_erofs_lzma_decomp = { .config = z_erofs_load_lzma_config, .decompress = z_erofs_lzma_decompress, .init = z_erofs_lzma_init, .exit = z_erofs_lzma_exit, .name = "lzma" };