162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci#include <linux/xz.h> 362306a36Sopenharmony_ci#include <linux/module.h> 462306a36Sopenharmony_ci#include "compress.h" 562306a36Sopenharmony_ci 662306a36Sopenharmony_cistruct z_erofs_lzma { 762306a36Sopenharmony_ci struct z_erofs_lzma *next; 862306a36Sopenharmony_ci struct xz_dec_microlzma *state; 962306a36Sopenharmony_ci struct xz_buf buf; 1062306a36Sopenharmony_ci u8 bounce[PAGE_SIZE]; 1162306a36Sopenharmony_ci}; 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci/* considering the LZMA performance, no need to use a lockless list for now */ 1462306a36Sopenharmony_cistatic DEFINE_SPINLOCK(z_erofs_lzma_lock); 1562306a36Sopenharmony_cistatic unsigned int z_erofs_lzma_max_dictsize; 1662306a36Sopenharmony_cistatic unsigned int z_erofs_lzma_nstrms, z_erofs_lzma_avail_strms; 1762306a36Sopenharmony_cistatic struct z_erofs_lzma *z_erofs_lzma_head; 1862306a36Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(z_erofs_lzma_wq); 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cimodule_param_named(lzma_streams, z_erofs_lzma_nstrms, uint, 0444); 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_civoid z_erofs_lzma_exit(void) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci /* there should be no running fs instance */ 2562306a36Sopenharmony_ci while (z_erofs_lzma_avail_strms) { 2662306a36Sopenharmony_ci struct z_erofs_lzma *strm; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci spin_lock(&z_erofs_lzma_lock); 2962306a36Sopenharmony_ci strm = z_erofs_lzma_head; 3062306a36Sopenharmony_ci if (!strm) { 3162306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 3262306a36Sopenharmony_ci DBG_BUGON(1); 3362306a36Sopenharmony_ci return; 3462306a36Sopenharmony_ci } 3562306a36Sopenharmony_ci z_erofs_lzma_head = NULL; 3662306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci while (strm) { 3962306a36Sopenharmony_ci struct z_erofs_lzma *n = strm->next; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci if (strm->state) 4262306a36Sopenharmony_ci xz_dec_microlzma_end(strm->state); 4362306a36Sopenharmony_ci kfree(strm); 4462306a36Sopenharmony_ci --z_erofs_lzma_avail_strms; 4562306a36Sopenharmony_ci strm = n; 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ciint __init z_erofs_lzma_init(void) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci unsigned int i; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci /* by default, use # of possible CPUs instead */ 5562306a36Sopenharmony_ci if (!z_erofs_lzma_nstrms) 5662306a36Sopenharmony_ci z_erofs_lzma_nstrms = num_possible_cpus(); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci for (i = 0; i < z_erofs_lzma_nstrms; ++i) { 5962306a36Sopenharmony_ci struct z_erofs_lzma *strm = kzalloc(sizeof(*strm), GFP_KERNEL); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (!strm) { 6262306a36Sopenharmony_ci z_erofs_lzma_exit(); 6362306a36Sopenharmony_ci return -ENOMEM; 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci spin_lock(&z_erofs_lzma_lock); 6662306a36Sopenharmony_ci strm->next = z_erofs_lzma_head; 6762306a36Sopenharmony_ci z_erofs_lzma_head = strm; 6862306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 6962306a36Sopenharmony_ci ++z_erofs_lzma_avail_strms; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci return 0; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ciint z_erofs_load_lzma_config(struct super_block *sb, 7562306a36Sopenharmony_ci struct erofs_super_block *dsb, void *data, int size) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci static DEFINE_MUTEX(lzma_resize_mutex); 7862306a36Sopenharmony_ci struct z_erofs_lzma_cfgs *lzma = data; 7962306a36Sopenharmony_ci unsigned int dict_size, i; 8062306a36Sopenharmony_ci struct z_erofs_lzma *strm, *head = NULL; 8162306a36Sopenharmony_ci int err; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci if (!lzma || size < sizeof(struct z_erofs_lzma_cfgs)) { 8462306a36Sopenharmony_ci erofs_err(sb, "invalid lzma cfgs, size=%u", size); 8562306a36Sopenharmony_ci return -EINVAL; 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci if (lzma->format) { 8862306a36Sopenharmony_ci erofs_err(sb, "unidentified lzma format %x, please check kernel version", 8962306a36Sopenharmony_ci le16_to_cpu(lzma->format)); 9062306a36Sopenharmony_ci return -EINVAL; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci dict_size = le32_to_cpu(lzma->dict_size); 9362306a36Sopenharmony_ci if (dict_size > Z_EROFS_LZMA_MAX_DICT_SIZE || dict_size < 4096) { 9462306a36Sopenharmony_ci erofs_err(sb, "unsupported lzma dictionary size %u", 9562306a36Sopenharmony_ci dict_size); 9662306a36Sopenharmony_ci return -EINVAL; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci erofs_info(sb, "EXPERIMENTAL MicroLZMA in use. Use at your own risk!"); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci /* in case 2 z_erofs_load_lzma_config() race to avoid deadlock */ 10262306a36Sopenharmony_ci mutex_lock(&lzma_resize_mutex); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (z_erofs_lzma_max_dictsize >= dict_size) { 10562306a36Sopenharmony_ci mutex_unlock(&lzma_resize_mutex); 10662306a36Sopenharmony_ci return 0; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci /* 1. collect/isolate all streams for the following check */ 11062306a36Sopenharmony_ci for (i = 0; i < z_erofs_lzma_avail_strms; ++i) { 11162306a36Sopenharmony_ci struct z_erofs_lzma *last; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ciagain: 11462306a36Sopenharmony_ci spin_lock(&z_erofs_lzma_lock); 11562306a36Sopenharmony_ci strm = z_erofs_lzma_head; 11662306a36Sopenharmony_ci if (!strm) { 11762306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 11862306a36Sopenharmony_ci wait_event(z_erofs_lzma_wq, 11962306a36Sopenharmony_ci READ_ONCE(z_erofs_lzma_head)); 12062306a36Sopenharmony_ci goto again; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci z_erofs_lzma_head = NULL; 12362306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci for (last = strm; last->next; last = last->next) 12662306a36Sopenharmony_ci ++i; 12762306a36Sopenharmony_ci last->next = head; 12862306a36Sopenharmony_ci head = strm; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci err = 0; 13262306a36Sopenharmony_ci /* 2. walk each isolated stream and grow max dict_size if needed */ 13362306a36Sopenharmony_ci for (strm = head; strm; strm = strm->next) { 13462306a36Sopenharmony_ci if (strm->state) 13562306a36Sopenharmony_ci xz_dec_microlzma_end(strm->state); 13662306a36Sopenharmony_ci strm->state = xz_dec_microlzma_alloc(XZ_PREALLOC, dict_size); 13762306a36Sopenharmony_ci if (!strm->state) 13862306a36Sopenharmony_ci err = -ENOMEM; 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci /* 3. push back all to the global list and update max dict_size */ 14262306a36Sopenharmony_ci spin_lock(&z_erofs_lzma_lock); 14362306a36Sopenharmony_ci DBG_BUGON(z_erofs_lzma_head); 14462306a36Sopenharmony_ci z_erofs_lzma_head = head; 14562306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 14662306a36Sopenharmony_ci wake_up_all(&z_erofs_lzma_wq); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci z_erofs_lzma_max_dictsize = dict_size; 14962306a36Sopenharmony_ci mutex_unlock(&lzma_resize_mutex); 15062306a36Sopenharmony_ci return err; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ciint z_erofs_lzma_decompress(struct z_erofs_decompress_req *rq, 15462306a36Sopenharmony_ci struct page **pagepool) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci const unsigned int nrpages_out = 15762306a36Sopenharmony_ci PAGE_ALIGN(rq->pageofs_out + rq->outputsize) >> PAGE_SHIFT; 15862306a36Sopenharmony_ci const unsigned int nrpages_in = 15962306a36Sopenharmony_ci PAGE_ALIGN(rq->inputsize) >> PAGE_SHIFT; 16062306a36Sopenharmony_ci unsigned int inlen, outlen, pageofs; 16162306a36Sopenharmony_ci struct z_erofs_lzma *strm; 16262306a36Sopenharmony_ci u8 *kin; 16362306a36Sopenharmony_ci bool bounced = false; 16462306a36Sopenharmony_ci int no, ni, j, err = 0; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* 1. get the exact LZMA compressed size */ 16762306a36Sopenharmony_ci kin = kmap(*rq->in); 16862306a36Sopenharmony_ci err = z_erofs_fixup_insize(rq, kin + rq->pageofs_in, 16962306a36Sopenharmony_ci min_t(unsigned int, rq->inputsize, 17062306a36Sopenharmony_ci rq->sb->s_blocksize - rq->pageofs_in)); 17162306a36Sopenharmony_ci if (err) { 17262306a36Sopenharmony_ci kunmap(*rq->in); 17362306a36Sopenharmony_ci return err; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci /* 2. get an available lzma context */ 17762306a36Sopenharmony_ciagain: 17862306a36Sopenharmony_ci spin_lock(&z_erofs_lzma_lock); 17962306a36Sopenharmony_ci strm = z_erofs_lzma_head; 18062306a36Sopenharmony_ci if (!strm) { 18162306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 18262306a36Sopenharmony_ci wait_event(z_erofs_lzma_wq, READ_ONCE(z_erofs_lzma_head)); 18362306a36Sopenharmony_ci goto again; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci z_erofs_lzma_head = strm->next; 18662306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* 3. multi-call decompress */ 18962306a36Sopenharmony_ci inlen = rq->inputsize; 19062306a36Sopenharmony_ci outlen = rq->outputsize; 19162306a36Sopenharmony_ci xz_dec_microlzma_reset(strm->state, inlen, outlen, 19262306a36Sopenharmony_ci !rq->partial_decoding); 19362306a36Sopenharmony_ci pageofs = rq->pageofs_out; 19462306a36Sopenharmony_ci strm->buf.in = kin + rq->pageofs_in; 19562306a36Sopenharmony_ci strm->buf.in_pos = 0; 19662306a36Sopenharmony_ci strm->buf.in_size = min_t(u32, inlen, PAGE_SIZE - rq->pageofs_in); 19762306a36Sopenharmony_ci inlen -= strm->buf.in_size; 19862306a36Sopenharmony_ci strm->buf.out = NULL; 19962306a36Sopenharmony_ci strm->buf.out_pos = 0; 20062306a36Sopenharmony_ci strm->buf.out_size = 0; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci for (ni = 0, no = -1;;) { 20362306a36Sopenharmony_ci enum xz_ret xz_err; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci if (strm->buf.out_pos == strm->buf.out_size) { 20662306a36Sopenharmony_ci if (strm->buf.out) { 20762306a36Sopenharmony_ci kunmap(rq->out[no]); 20862306a36Sopenharmony_ci strm->buf.out = NULL; 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (++no >= nrpages_out || !outlen) { 21262306a36Sopenharmony_ci erofs_err(rq->sb, "decompressed buf out of bound"); 21362306a36Sopenharmony_ci err = -EFSCORRUPTED; 21462306a36Sopenharmony_ci break; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci strm->buf.out_pos = 0; 21762306a36Sopenharmony_ci strm->buf.out_size = min_t(u32, outlen, 21862306a36Sopenharmony_ci PAGE_SIZE - pageofs); 21962306a36Sopenharmony_ci outlen -= strm->buf.out_size; 22062306a36Sopenharmony_ci if (!rq->out[no] && rq->fillgaps) { /* deduped */ 22162306a36Sopenharmony_ci rq->out[no] = erofs_allocpage(pagepool, 22262306a36Sopenharmony_ci GFP_KERNEL | __GFP_NOFAIL); 22362306a36Sopenharmony_ci set_page_private(rq->out[no], 22462306a36Sopenharmony_ci Z_EROFS_SHORTLIVED_PAGE); 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci if (rq->out[no]) 22762306a36Sopenharmony_ci strm->buf.out = kmap(rq->out[no]) + pageofs; 22862306a36Sopenharmony_ci pageofs = 0; 22962306a36Sopenharmony_ci } else if (strm->buf.in_pos == strm->buf.in_size) { 23062306a36Sopenharmony_ci kunmap(rq->in[ni]); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (++ni >= nrpages_in || !inlen) { 23362306a36Sopenharmony_ci erofs_err(rq->sb, "compressed buf out of bound"); 23462306a36Sopenharmony_ci err = -EFSCORRUPTED; 23562306a36Sopenharmony_ci break; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci strm->buf.in_pos = 0; 23862306a36Sopenharmony_ci strm->buf.in_size = min_t(u32, inlen, PAGE_SIZE); 23962306a36Sopenharmony_ci inlen -= strm->buf.in_size; 24062306a36Sopenharmony_ci kin = kmap(rq->in[ni]); 24162306a36Sopenharmony_ci strm->buf.in = kin; 24262306a36Sopenharmony_ci bounced = false; 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci /* 24662306a36Sopenharmony_ci * Handle overlapping: Use bounced buffer if the compressed 24762306a36Sopenharmony_ci * data is under processing; Otherwise, Use short-lived pages 24862306a36Sopenharmony_ci * from the on-stack pagepool where pages share with the same 24962306a36Sopenharmony_ci * request. 25062306a36Sopenharmony_ci */ 25162306a36Sopenharmony_ci if (!bounced && rq->out[no] == rq->in[ni]) { 25262306a36Sopenharmony_ci memcpy(strm->bounce, strm->buf.in, strm->buf.in_size); 25362306a36Sopenharmony_ci strm->buf.in = strm->bounce; 25462306a36Sopenharmony_ci bounced = true; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci for (j = ni + 1; j < nrpages_in; ++j) { 25762306a36Sopenharmony_ci struct page *tmppage; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci if (rq->out[no] != rq->in[j]) 26062306a36Sopenharmony_ci continue; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci DBG_BUGON(erofs_page_is_managed(EROFS_SB(rq->sb), 26362306a36Sopenharmony_ci rq->in[j])); 26462306a36Sopenharmony_ci tmppage = erofs_allocpage(pagepool, 26562306a36Sopenharmony_ci GFP_KERNEL | __GFP_NOFAIL); 26662306a36Sopenharmony_ci set_page_private(tmppage, Z_EROFS_SHORTLIVED_PAGE); 26762306a36Sopenharmony_ci copy_highpage(tmppage, rq->in[j]); 26862306a36Sopenharmony_ci rq->in[j] = tmppage; 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci xz_err = xz_dec_microlzma_run(strm->state, &strm->buf); 27162306a36Sopenharmony_ci DBG_BUGON(strm->buf.out_pos > strm->buf.out_size); 27262306a36Sopenharmony_ci DBG_BUGON(strm->buf.in_pos > strm->buf.in_size); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci if (xz_err != XZ_OK) { 27562306a36Sopenharmony_ci if (xz_err == XZ_STREAM_END && !outlen) 27662306a36Sopenharmony_ci break; 27762306a36Sopenharmony_ci erofs_err(rq->sb, "failed to decompress %d in[%u] out[%u]", 27862306a36Sopenharmony_ci xz_err, rq->inputsize, rq->outputsize); 27962306a36Sopenharmony_ci err = -EFSCORRUPTED; 28062306a36Sopenharmony_ci break; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci if (no < nrpages_out && strm->buf.out) 28462306a36Sopenharmony_ci kunmap(rq->out[no]); 28562306a36Sopenharmony_ci if (ni < nrpages_in) 28662306a36Sopenharmony_ci kunmap(rq->in[ni]); 28762306a36Sopenharmony_ci /* 4. push back LZMA stream context to the global list */ 28862306a36Sopenharmony_ci spin_lock(&z_erofs_lzma_lock); 28962306a36Sopenharmony_ci strm->next = z_erofs_lzma_head; 29062306a36Sopenharmony_ci z_erofs_lzma_head = strm; 29162306a36Sopenharmony_ci spin_unlock(&z_erofs_lzma_lock); 29262306a36Sopenharmony_ci wake_up(&z_erofs_lzma_wq); 29362306a36Sopenharmony_ci return err; 29462306a36Sopenharmony_ci} 295