summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/staging/erofs/decompressor.c50
1 files changed, 28 insertions, 22 deletions
diff --git a/drivers/staging/erofs/decompressor.c b/drivers/staging/erofs/decompressor.c
index 80f1f39719ba..1fb0abb98dff 100644
--- a/drivers/staging/erofs/decompressor.c
+++ b/drivers/staging/erofs/decompressor.c
@@ -13,7 +13,7 @@
#define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */
#endif
-#define LZ4_MAX_DISTANCE_PAGES DIV_ROUND_UP(LZ4_DISTANCE_MAX, PAGE_SIZE)
+#define LZ4_MAX_DISTANCE_PAGES (DIV_ROUND_UP(LZ4_DISTANCE_MAX, PAGE_SIZE) + 1)
#ifndef LZ4_DECOMPRESS_INPLACE_MARGIN
#define LZ4_DECOMPRESS_INPLACE_MARGIN(srcsize) (((srcsize) >> 8) + 32)
#endif
@@ -35,19 +35,28 @@ static int lz4_prepare_destpages(struct z_erofs_decompress_req *rq,
const unsigned int nr =
PAGE_ALIGN(rq->pageofs_out + rq->outputsize) >> PAGE_SHIFT;
struct page *availables[LZ4_MAX_DISTANCE_PAGES] = { NULL };
- unsigned long unused[DIV_ROUND_UP(LZ4_MAX_DISTANCE_PAGES,
- BITS_PER_LONG)] = { 0 };
+ unsigned long bounced[DIV_ROUND_UP(LZ4_MAX_DISTANCE_PAGES,
+ BITS_PER_LONG)] = { 0 };
void *kaddr = NULL;
- unsigned int i, j, k;
+ unsigned int i, j, top;
- for (i = 0; i < nr; ++i) {
+ top = 0;
+ for (i = j = 0; i < nr; ++i, ++j) {
struct page *const page = rq->out[i];
+ struct page *victim;
- j = i & (LZ4_MAX_DISTANCE_PAGES - 1);
- if (availables[j])
- __set_bit(j, unused);
+ if (j >= LZ4_MAX_DISTANCE_PAGES)
+ j = 0;
+
+ /* 'valid' bounced can only be tested after a complete round */
+ if (test_bit(j, bounced)) {
+ DBG_BUGON(i < LZ4_MAX_DISTANCE_PAGES);
+ DBG_BUGON(top >= LZ4_MAX_DISTANCE_PAGES);
+ availables[top++] = rq->out[i - LZ4_MAX_DISTANCE_PAGES];
+ }
if (page) {
+ __clear_bit(j, bounced);
if (kaddr) {
if (kaddr + PAGE_SIZE == page_address(page))
kaddr += PAGE_SIZE;
@@ -59,27 +68,24 @@ static int lz4_prepare_destpages(struct z_erofs_decompress_req *rq,
continue;
}
kaddr = NULL;
+ __set_bit(j, bounced);
- k = find_first_bit(unused, LZ4_MAX_DISTANCE_PAGES);
- if (k < LZ4_MAX_DISTANCE_PAGES) {
- j = k;
- get_page(availables[j]);
+ if (top) {
+ victim = availables[--top];
+ get_page(victim);
} else {
- DBG_BUGON(availables[j]);
-
if (!list_empty(pagepool)) {
- availables[j] = lru_to_page(pagepool);
- list_del(&availables[j]->lru);
- DBG_BUGON(page_ref_count(availables[j]) != 1);
+ victim = lru_to_page(pagepool);
+ list_del(&victim->lru);
+ DBG_BUGON(page_ref_count(victim) != 1);
} else {
- availables[j] = alloc_pages(GFP_KERNEL, 0);
- if (!availables[j])
+ victim = alloc_pages(GFP_KERNEL, 0);
+ if (!victim)
return -ENOMEM;
}
- availables[j]->mapping = Z_EROFS_MAPPING_STAGING;
+ victim->mapping = Z_EROFS_MAPPING_STAGING;
}
- rq->out[i] = availables[j];
- __clear_bit(j, unused);
+ rq->out[i] = victim;
}
return kaddr ? 1 : 0;
}