| // SPDX-License-Identifier: Zlib | |
| #include "../zlib_deflate/defutil.h" | |
| #include "dfltcc_util.h" | |
| #include "dfltcc_deflate.h" | |
| #include <asm/setup.h> | |
| #include <linux/export.h> | |
| #include <linux/zutil.h> | |
| #define GET_DFLTCC_DEFLATE_STATE(state) ((struct dfltcc_deflate_state *)GET_DFLTCC_STATE(state)) | |
| /* | |
| * Compress. | |
| */ | |
| int dfltcc_can_deflate( | |
| z_streamp strm | |
| ) | |
| { | |
| deflate_state *state = (deflate_state *)strm->state; | |
| struct dfltcc_deflate_state *dfltcc_state = GET_DFLTCC_DEFLATE_STATE(state); | |
| /* Check for kernel dfltcc command line parameter */ | |
| if (zlib_dfltcc_support == ZLIB_DFLTCC_DISABLED || | |
| zlib_dfltcc_support == ZLIB_DFLTCC_INFLATE_ONLY) | |
| return 0; | |
| /* Unsupported compression settings */ | |
| if (!dfltcc_are_params_ok(state->level, state->w_bits, state->strategy, | |
| dfltcc_state->level_mask)) | |
| return 0; | |
| /* Unsupported hardware */ | |
| if (!is_bit_set(dfltcc_state->common.af.fns, DFLTCC_GDHT) || | |
| !is_bit_set(dfltcc_state->common.af.fns, DFLTCC_CMPR) || | |
| !is_bit_set(dfltcc_state->common.af.fmts, DFLTCC_FMT0)) | |
| return 0; | |
| return 1; | |
| } | |
| EXPORT_SYMBOL(dfltcc_can_deflate); | |
| void dfltcc_reset_deflate_state(z_streamp strm) { | |
| deflate_state *state = (deflate_state *)strm->state; | |
| struct dfltcc_deflate_state *dfltcc_state = GET_DFLTCC_DEFLATE_STATE(state); | |
| dfltcc_reset_state(&dfltcc_state->common); | |
| /* Initialize tuning parameters */ | |
| if (zlib_dfltcc_support == ZLIB_DFLTCC_FULL_DEBUG) | |
| dfltcc_state->level_mask = DFLTCC_LEVEL_MASK_DEBUG; | |
| else | |
| dfltcc_state->level_mask = DFLTCC_LEVEL_MASK; | |
| dfltcc_state->block_size = DFLTCC_BLOCK_SIZE; | |
| dfltcc_state->block_threshold = DFLTCC_FIRST_FHT_BLOCK_SIZE; | |
| dfltcc_state->dht_threshold = DFLTCC_DHT_MIN_SAMPLE_SIZE; | |
| } | |
| EXPORT_SYMBOL(dfltcc_reset_deflate_state); | |
| static void dfltcc_gdht( | |
| z_streamp strm | |
| ) | |
| { | |
| deflate_state *state = (deflate_state *)strm->state; | |
| struct dfltcc_param_v0 *param = &GET_DFLTCC_STATE(state)->param; | |
| size_t avail_in = strm->avail_in; | |
| dfltcc(DFLTCC_GDHT, | |
| param, NULL, NULL, | |
| &strm->next_in, &avail_in, NULL); | |
| } | |
| static dfltcc_cc dfltcc_cmpr( | |
| z_streamp strm | |
| ) | |
| { | |
| deflate_state *state = (deflate_state *)strm->state; | |
| struct dfltcc_param_v0 *param = &GET_DFLTCC_STATE(state)->param; | |
| size_t avail_in = strm->avail_in; | |
| size_t avail_out = strm->avail_out; | |
| dfltcc_cc cc; | |
| cc = dfltcc(DFLTCC_CMPR | HBT_CIRCULAR, | |
| param, &strm->next_out, &avail_out, | |
| &strm->next_in, &avail_in, state->window); | |
| strm->total_in += (strm->avail_in - avail_in); | |
| strm->total_out += (strm->avail_out - avail_out); | |
| strm->avail_in = avail_in; | |
| strm->avail_out = avail_out; | |
| return cc; | |
| } | |
| static void send_eobs( | |
| z_streamp strm, | |
| const struct dfltcc_param_v0 *param | |
| ) | |
| { | |
| deflate_state *state = (deflate_state *)strm->state; | |
| zlib_tr_send_bits( | |
| state, | |
| bi_reverse(param->eobs >> (15 - param->eobl), param->eobl), | |
| param->eobl); | |
| flush_pending(strm); | |
| if (state->pending != 0) { | |
| /* The remaining data is located in pending_out[0:pending]. If someone | |
| * calls put_byte() - this might happen in deflate() - the byte will be | |
| * placed into pending_buf[pending], which is incorrect. Move the | |
| * remaining data to the beginning of pending_buf so that put_byte() is | |
| * usable again. | |
| */ | |
| memmove(state->pending_buf, state->pending_out, state->pending); | |
| state->pending_out = state->pending_buf; | |
| } | |
| #ifdef ZLIB_DEBUG | |
| state->compressed_len += param->eobl; | |
| #endif | |
| } | |
| int dfltcc_deflate( | |
| z_streamp strm, | |
| int flush, | |
| block_state *result | |
| ) | |
| { | |
| deflate_state *state = (deflate_state *)strm->state; | |
| struct dfltcc_deflate_state *dfltcc_state = GET_DFLTCC_DEFLATE_STATE(state); | |
| struct dfltcc_param_v0 *param = &dfltcc_state->common.param; | |
| uInt masked_avail_in; | |
| dfltcc_cc cc; | |
| int need_empty_block; | |
| int soft_bcc; | |
| int no_flush; | |
| if (!dfltcc_can_deflate(strm)) { | |
| /* Clear history. */ | |
| if (flush == Z_FULL_FLUSH) | |
| param->hl = 0; | |
| return 0; | |
| } | |
| again: | |
| masked_avail_in = 0; | |
| soft_bcc = 0; | |
| no_flush = flush == Z_NO_FLUSH; | |
| /* No input data. Return, except when Continuation Flag is set, which means | |
| * that DFLTCC has buffered some output in the parameter block and needs to | |
| * be called again in order to flush it. | |
| */ | |
| if (strm->avail_in == 0 && !param->cf) { | |
| /* A block is still open, and the hardware does not support closing | |
| * blocks without adding data. Thus, close it manually. | |
| */ | |
| if (!no_flush && param->bcf) { | |
| send_eobs(strm, param); | |
| param->bcf = 0; | |
| } | |
| /* Let one of deflate_* functions write a trailing empty block. */ | |
| if (flush == Z_FINISH) | |
| return 0; | |
| /* Clear history. */ | |
| if (flush == Z_FULL_FLUSH) | |
| param->hl = 0; | |
| /* Trigger block post-processing if necessary. */ | |
| *result = no_flush ? need_more : block_done; | |
| return 1; | |
| } | |
| /* There is an open non-BFINAL block, we are not going to close it just | |
| * yet, we have compressed more than DFLTCC_BLOCK_SIZE bytes and we see | |
| * more than DFLTCC_DHT_MIN_SAMPLE_SIZE bytes. Open a new block with a new | |
| * DHT in order to adapt to a possibly changed input data distribution. | |
| */ | |
| if (param->bcf && no_flush && | |
| strm->total_in > dfltcc_state->block_threshold && | |
| strm->avail_in >= dfltcc_state->dht_threshold) { | |
| if (param->cf) { | |
| /* We need to flush the DFLTCC buffer before writing the | |
| * End-of-block Symbol. Mask the input data and proceed as usual. | |
| */ | |
| masked_avail_in += strm->avail_in; | |
| strm->avail_in = 0; | |
| no_flush = 0; | |
| } else { | |
| /* DFLTCC buffer is empty, so we can manually write the | |
| * End-of-block Symbol right away. | |
| */ | |
| send_eobs(strm, param); | |
| param->bcf = 0; | |
| dfltcc_state->block_threshold = | |
| strm->total_in + dfltcc_state->block_size; | |
| } | |
| } | |
| /* No space for compressed data. If we proceed, dfltcc_cmpr() will return | |
| * DFLTCC_CC_OP1_TOO_SHORT without buffering header bits, but we will still | |
| * set BCF=1, which is wrong. Avoid complications and return early. | |
| */ | |
| if (strm->avail_out == 0) { | |
| *result = need_more; | |
| return 1; | |
| } | |
| /* The caller gave us too much data. Pass only one block worth of | |
| * uncompressed data to DFLTCC and mask the rest, so that on the next | |
| * iteration we start a new block. | |
| */ | |
| if (no_flush && strm->avail_in > dfltcc_state->block_size) { | |
| masked_avail_in += (strm->avail_in - dfltcc_state->block_size); | |
| strm->avail_in = dfltcc_state->block_size; | |
| } | |
| /* When we have an open non-BFINAL deflate block and caller indicates that | |
| * the stream is ending, we need to close an open deflate block and open a | |
| * BFINAL one. | |
| */ | |
| need_empty_block = flush == Z_FINISH && param->bcf && !param->bhf; | |
| /* Translate stream to parameter block */ | |
| param->cvt = CVT_ADLER32; | |
| if (!no_flush) | |
| /* We need to close a block. Always do this in software - when there is | |
| * no input data, the hardware will not hohor BCC. */ | |
| soft_bcc = 1; | |
| if (flush == Z_FINISH && !param->bcf) | |
| /* We are about to open a BFINAL block, set Block Header Final bit | |
| * until the stream ends. | |
| */ | |
| param->bhf = 1; | |
| /* DFLTCC-CMPR will write to next_out, so make sure that buffers with | |
| * higher precedence are empty. | |
| */ | |
| Assert(state->pending == 0, "There must be no pending bytes"); | |
| Assert(state->bi_valid < 8, "There must be less than 8 pending bits"); | |
| param->sbb = (unsigned int)state->bi_valid; | |
| if (param->sbb > 0) | |
| *strm->next_out = (Byte)state->bi_buf; | |
| /* Honor history and check value */ | |
| param->nt = 0; | |
| param->cv = strm->adler; | |
| /* When opening a block, choose a Huffman-Table Type */ | |
| if (!param->bcf) { | |
| if (strm->total_in == 0 && dfltcc_state->block_threshold > 0) { | |
| param->htt = HTT_FIXED; | |
| } | |
| else { | |
| param->htt = HTT_DYNAMIC; | |
| dfltcc_gdht(strm); | |
| } | |
| } | |
| /* Deflate */ | |
| do { | |
| cc = dfltcc_cmpr(strm); | |
| if (strm->avail_in < 4096 && masked_avail_in > 0) | |
| /* We are about to call DFLTCC with a small input buffer, which is | |
| * inefficient. Since there is masked data, there will be at least | |
| * one more DFLTCC call, so skip the current one and make the next | |
| * one handle more data. | |
| */ | |
| break; | |
| } while (cc == DFLTCC_CC_AGAIN); | |
| /* Translate parameter block to stream */ | |
| strm->msg = oesc_msg(dfltcc_state->common.msg, param->oesc); | |
| state->bi_valid = param->sbb; | |
| if (state->bi_valid == 0) | |
| state->bi_buf = 0; /* Avoid accessing next_out */ | |
| else | |
| state->bi_buf = *strm->next_out & ((1 << state->bi_valid) - 1); | |
| strm->adler = param->cv; | |
| /* Unmask the input data */ | |
| strm->avail_in += masked_avail_in; | |
| masked_avail_in = 0; | |
| /* If we encounter an error, it means there is a bug in DFLTCC call */ | |
| Assert(cc != DFLTCC_CC_OP2_CORRUPT || param->oesc == 0, "BUG"); | |
| /* Update Block-Continuation Flag. It will be used to check whether to call | |
| * GDHT the next time. | |
| */ | |
| if (cc == DFLTCC_CC_OK) { | |
| if (soft_bcc) { | |
| send_eobs(strm, param); | |
| param->bcf = 0; | |
| dfltcc_state->block_threshold = | |
| strm->total_in + dfltcc_state->block_size; | |
| } else | |
| param->bcf = 1; | |
| if (flush == Z_FINISH) { | |
| if (need_empty_block) | |
| /* Make the current deflate() call also close the stream */ | |
| return 0; | |
| else { | |
| bi_windup(state); | |
| *result = finish_done; | |
| } | |
| } else { | |
| if (flush == Z_FULL_FLUSH) | |
| param->hl = 0; /* Clear history */ | |
| *result = flush == Z_NO_FLUSH ? need_more : block_done; | |
| } | |
| } else { | |
| param->bcf = 1; | |
| *result = need_more; | |
| } | |
| if (strm->avail_in != 0 && strm->avail_out != 0) | |
| goto again; /* deflate() must use all input or all output */ | |
| return 1; | |
| } | |
| EXPORT_SYMBOL(dfltcc_deflate); |