| // SPDX-License-Identifier: GPL-2.0-only |
| /* Buffering helpers for bvec queues |
| * |
| * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. |
| * Written by David Howells (dhowells@redhat.com) |
| */ |
| |
| #include "internal.h" |
| |
| void bvecq_dump(const struct bvecq *bq) |
| { |
| int b = 0; |
| |
| for (; bq; bq = bq->next, b++) { |
| int skipz = 0; |
| |
| pr_notice("BQ[%u] %u/%u fp=%llx\n", b, bq->nr_slots, bq->max_slots, bq->fpos); |
| for (int s = 0; s < bq->nr_slots; s++) { |
| const struct bio_vec *bv = &bq->bv[s]; |
| |
| if (!bv->bv_page && !bv->bv_len && skipz < 2) { |
| skipz = 1; |
| continue; |
| } |
| if (skipz == 1) |
| pr_notice("BQ[%u:00-%02u] ...\n", b, s - 1); |
| skipz = 2; |
| pr_notice("BQ[%u:%02u] %10lx %04x %04x %u\n", |
| b, s, |
| bv->bv_page ? page_to_pfn(bv->bv_page) : 0, |
| bv->bv_offset, bv->bv_len, |
| bv->bv_page ? page_count(bv->bv_page) : 0); |
| } |
| } |
| } |
| EXPORT_SYMBOL(bvecq_dump); |
| |
| /** |
| * bvecq_alloc_one - Allocate a single bvecq node with unpopulated slots |
| * @nr_slots: Number of slots to allocate |
| * @gfp: The allocation constraints. |
| * |
| * Allocate a single bvecq node and initialise the header. A number of inline |
| * slots are also allocated, rounded up to fit after the header in a power-of-2 |
| * slab object of up to 512 bytes (up to 29 slots on a 64-bit cpu). The slot |
| * array is not initialised. |
| * |
| * Return: The node pointer or NULL on allocation failure. |
| */ |
| struct bvecq *bvecq_alloc_one(size_t nr_slots, gfp_t gfp) |
| { |
| struct bvecq *bq; |
| const size_t max_size = 512; |
| const size_t max_slots = (max_size - sizeof(*bq)) / sizeof(bq->__bv[0]); |
| size_t part = umin(nr_slots, max_slots); |
| size_t size = roundup_pow_of_two(struct_size(bq, __bv, part)); |
| |
| bq = kmalloc(size, gfp); |
| if (bq) { |
| *bq = (struct bvecq) { |
| .ref = REFCOUNT_INIT(1), |
| .bv = bq->__bv, |
| .inline_bv = true, |
| .max_slots = (size - sizeof(*bq)) / sizeof(bq->__bv[0]), |
| }; |
| netfs_stat(&netfs_n_bvecq); |
| } |
| return bq; |
| } |
| EXPORT_SYMBOL(bvecq_alloc_one); |
| |
| /** |
| * bvecq_alloc_chain - Allocate an unpopulated bvecq chain |
| * @nr_slots: Number of slots to allocate |
| * @gfp: The allocation constraints. |
| * |
| * Allocate a chain of bvecq nodes providing at least the requested cumulative |
| * number of slots. |
| * |
| * Return: The first node pointer or NULL on allocation failure. |
| */ |
| struct bvecq *bvecq_alloc_chain(size_t nr_slots, gfp_t gfp) |
| { |
| struct bvecq *head = NULL, *tail = NULL; |
| |
| _enter("%zu", nr_slots); |
| |
| for (;;) { |
| struct bvecq *bq; |
| |
| bq = bvecq_alloc_one(nr_slots, gfp); |
| if (!bq) |
| goto oom; |
| |
| if (tail) { |
| tail->next = bq; |
| bq->prev = tail; |
| } else { |
| head = bq; |
| } |
| tail = bq; |
| if (tail->max_slots >= nr_slots) |
| break; |
| nr_slots -= tail->max_slots; |
| } |
| |
| return head; |
| oom: |
| bvecq_put(head); |
| return NULL; |
| } |
| EXPORT_SYMBOL(bvecq_alloc_chain); |
| |
| /** |
| * bvecq_alloc_buffer - Allocate a bvecq chain and populate with buffers |
| * @size: Target size of the buffer (can be 0 for an empty buffer) |
| * @pre_slots: Number of preamble slots to set aside |
| * @gfp: The allocation constraints. |
| * |
| * Allocate a chain of bvecq nodes and populate the slots with sufficient pages |
| * to provide at least the requested amount of space, leaving the first |
| * @pre_slots slots unset. The pages allocated may be compound pages larger |
| * than PAGE_SIZE and thus occupy fewer slots. The pages have their refcounts |
| * set to 1 and can be passed to MSG_SPLICE_PAGES. |
| * |
| * Return: The first node pointer or NULL on allocation failure. |
| */ |
| struct bvecq *bvecq_alloc_buffer(size_t size, unsigned int pre_slots, gfp_t gfp) |
| { |
| struct bvecq *head = NULL, *tail = NULL, *p = NULL; |
| size_t count = DIV_ROUND_UP(size, PAGE_SIZE); |
| |
| _enter("%zx,%zx,%u", size, count, pre_slots); |
| |
| do { |
| struct page **pages; |
| int want, got; |
| |
| p = bvecq_alloc_one(umin(count, 32 - 3), gfp); |
| if (!p) |
| goto oom; |
| |
| p->free = true; |
| |
| if (tail) { |
| tail->next = p; |
| p->prev = tail; |
| } else { |
| head = p; |
| } |
| tail = p; |
| if (!count) |
| break; |
| |
| pages = (struct page **)&p->bv[p->max_slots]; |
| pages -= p->max_slots - pre_slots; |
| |
| want = umin(count, p->max_slots - pre_slots); |
| got = alloc_pages_bulk(gfp, want, pages); |
| if (got < want) { |
| for (int i = 0; i < got; i++) |
| __free_page(pages[i]); |
| goto oom; |
| } |
| |
| tail->nr_slots = pre_slots + got; |
| for (int i = 0; i < got; i++) { |
| int j = pre_slots + i; |
| |
| set_page_count(pages[i], 1); |
| bvec_set_page(&tail->bv[j], pages[i], PAGE_SIZE, 0); |
| } |
| |
| count -= got; |
| pre_slots = 0; |
| } while (count > 0); |
| |
| return head; |
| oom: |
| bvecq_put(head); |
| return NULL; |
| } |
| EXPORT_SYMBOL(bvecq_alloc_buffer); |
| |
| /* |
| * Free the page pointed to be a segment as necessary. |
| */ |
| static void bvecq_free_seg(struct bvecq *bq, unsigned int seg) |
| { |
| struct page *page = bq->bv[seg].bv_page; |
| int order = compound_order(page); |
| |
| if (!bq->free || !page) |
| return; |
| |
| if (bq->unpin) |
| unpin_user_page(page); |
| else if (order > 0) |
| __free_pages(page, order); |
| else |
| mempool_free(page, &netfs_page_pool); |
| } |
| |
| /** |
| * bvecq_put - Put a ref on a bvec queue |
| * @bq: The start of the folio queue to free |
| * |
| * Put the ref(s) on the nodes in a bvec queue, freeing up the node and the |
| * page fragments it points to as the refcounts become zero. |
| */ |
| void bvecq_put(struct bvecq *bq) |
| { |
| struct bvecq *next; |
| |
| for (; bq; bq = next) { |
| if (!refcount_dec_and_test(&bq->ref)) |
| break; |
| for (int seg = 0; seg < bq->nr_slots; seg++) |
| bvecq_free_seg(bq, seg); |
| next = bq->next; |
| netfs_stat_d(&netfs_n_bvecq); |
| kfree(bq); |
| } |
| } |
| EXPORT_SYMBOL(bvecq_put); |
| |
| /** |
| * bvecq_expand_buffer - Allocate buffer space into a bvec queue |
| * @_buffer: Pointer to the bvecq chain to expand (may point to a NULL; updated). |
| * @_cur_size: Current size of the buffer (updated). |
| * @size: Target size of the buffer. |
| * @gfp: The allocation constraints. |
| */ |
| int bvecq_expand_buffer(struct bvecq **_buffer, size_t *_cur_size, ssize_t size, gfp_t gfp) |
| { |
| struct bvecq *tail = *_buffer; |
| const size_t max_slots = 32; |
| |
| size = round_up(size, PAGE_SIZE); |
| if (*_cur_size >= size) |
| return 0; |
| |
| if (tail) |
| while (tail->next) |
| tail = tail->next; |
| |
| do { |
| struct page *page; |
| int order = 0; |
| |
| if (!tail || bvecq_is_full(tail)) { |
| struct bvecq *p; |
| |
| p = bvecq_alloc_one(max_slots, gfp); |
| if (!p) |
| return -ENOMEM; |
| if (tail) { |
| tail->next = p; |
| p->prev = tail; |
| } else { |
| *_buffer = p; |
| } |
| tail = p; |
| } |
| |
| if (size - *_cur_size > PAGE_SIZE) |
| order = umin(ilog2(size - *_cur_size) - PAGE_SHIFT, |
| MAX_PAGECACHE_ORDER); |
| |
| page = alloc_pages(gfp | __GFP_COMP, order); |
| if (!page && order > 0) { |
| if (gfp == GFP_KERNEL) |
| page = alloc_pages(gfp | __GFP_COMP, 0); |
| else |
| page = mempool_alloc(&netfs_page_pool, gfp | __GFP_COMP); |
| } |
| if (!page) |
| return -ENOMEM; |
| |
| bvec_set_page(&tail->bv[tail->nr_slots++], page, PAGE_SIZE << order, 0); |
| *_cur_size += PAGE_SIZE << order; |
| } while (*_cur_size < size); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(bvecq_expand_buffer); |
| |
| /** |
| * bvecq_shorten_buffer - Shorten a bvec queue buffer |
| * @bq: The start of the buffer to shorten |
| * @slot: The slot to start from |
| * @size: The size to retain |
| * |
| * Shorten the content of a bvec queue down to the minimum number of segments, |
| * starting at the specified segment, to retain the specified size. |
| * |
| * Return: 0 if successful; -EMSGSIZE if there is insufficient content. |
| */ |
| int bvecq_shorten_buffer(struct bvecq *bq, unsigned int slot, size_t size) |
| { |
| ssize_t retain = size; |
| |
| /* Skip through the segments we want to keep. */ |
| for (; bq; bq = bq->next) { |
| for (; slot < bq->nr_slots; slot++) { |
| retain -= bq->bv[slot].bv_len; |
| if (retain < 0) |
| goto found; |
| } |
| slot = 0; |
| } |
| if (WARN_ON_ONCE(retain > 0)) |
| return -EMSGSIZE; |
| return 0; |
| |
| found: |
| /* Shorten the entry to be retained and clean the rest of this bvecq. */ |
| bq->bv[slot].bv_len += retain; |
| slot++; |
| for (int i = slot; i < bq->nr_slots; i++) |
| bvecq_free_seg(bq, i); |
| bq->nr_slots = slot; |
| |
| /* Free the queue tail. */ |
| bvecq_put(bq->next); |
| bq->next = NULL; |
| return 0; |
| } |
| EXPORT_SYMBOL(bvecq_shorten_buffer); |
| |
| /** |
| * bvecq_buffer_init - Initialise a buffer and set position |
| * @pos: The position to point at the new buffer. |
| * @gfp: The allocation constraints. |
| * |
| * Initialise a rolling buffer. We allocate an unpopulated bvecq node to so |
| * that the pointers can be independently driven by the producer and the |
| * consumer. |
| * |
| * Return 0 if successful; -ENOMEM on allocation failure. |
| */ |
| int bvecq_buffer_init(struct bvecq_pos *pos, gfp_t gfp) |
| { |
| struct bvecq *bq; |
| |
| bq = bvecq_alloc_one(13, gfp); |
| if (!bq) |
| return -ENOMEM; |
| |
| pos->bvecq = bq; /* Comes with a ref. */ |
| pos->slot = 0; |
| pos->offset = 0; |
| return 0; |
| } |
| |
| /** |
| * bvecq_buffer_make_space - Start a new bvecq node in a buffer |
| * @pos: The position of the last node. |
| * @gfp: The allocation constraints. |
| * |
| * Add a new node on to the buffer chain at the specified position, either |
| * because the previous one is full or because we have a discontiguity to |
| * contend with, and update @pos to point to it. |
| * |
| * Return: 0 if successful; -ENOMEM on allocation failure. |
| */ |
| int bvecq_buffer_make_space(struct bvecq_pos *pos, gfp_t gfp) |
| { |
| struct bvecq *bq, *head = pos->bvecq; |
| |
| bq = bvecq_alloc_one(14, gfp); |
| if (!bq) |
| return -ENOMEM; |
| bq->prev = head; |
| |
| pos->bvecq = bvecq_get(bq); |
| pos->slot = 0; |
| pos->offset = 0; |
| |
| /* Make sure the initialisation is stored before the next pointer. |
| * |
| * [!] NOTE: After we set head->next, the consumer is at liberty to |
| * immediately delete the old head. |
| */ |
| smp_store_release(&head->next, bq); |
| bvecq_put(head); |
| return 0; |
| } |
| |
| /** |
| * bvecq_pos_advance - Advance a bvecq position |
| * @pos: The position to advance. |
| * @amount: The amount of bytes to advance by. |
| * |
| * Advance the specified bvecq position by @amount bytes. @pos is updated and |
| * bvecq ref counts may have been manipulated. If the position hits the end of |
| * the queue, then it is left pointing beyond the last slot of the last bvecq |
| * so that it doesn't break the chain. |
| */ |
| void bvecq_pos_advance(struct bvecq_pos *pos, size_t amount) |
| { |
| struct bvecq *bq = pos->bvecq; |
| unsigned int slot = pos->slot; |
| size_t offset = pos->offset; |
| |
| if (slot >= bq->nr_slots) { |
| bq = bq->next; |
| slot = 0; |
| } |
| |
| while (amount) { |
| const struct bio_vec *bv = &bq->bv[slot]; |
| size_t part = umin(bv->bv_len - offset, amount); |
| |
| if (likely(part < bv->bv_len)) { |
| offset += part; |
| break; |
| } |
| amount -= part; |
| offset = 0; |
| slot++; |
| if (slot >= bq->nr_slots) { |
| if (!bq->next) |
| break; |
| bq = bq->next; |
| slot = 0; |
| } |
| } |
| |
| pos->slot = slot; |
| pos->offset = offset; |
| bvecq_pos_move(pos, bq); |
| } |
| |
| /** |
| * bvecq_zero - Clear memory starting at the bvecq position. |
| * @pos: The position in the bvecq chain to start clearing. |
| * @amount: The number of bytes to clear. |
| * |
| * Clear memory fragments pointed to by a bvec queue. @pos is updated and |
| * bvecq ref counts may have been manipulated. If the position hits the end of |
| * the queue, then it is left pointing beyond the last slot of the last bvecq |
| * so that it doesn't break the chain. |
| * |
| * Return: The number of bytes cleared. |
| */ |
| ssize_t bvecq_zero(struct bvecq_pos *pos, size_t amount) |
| { |
| struct bvecq *bq = pos->bvecq; |
| unsigned int slot = pos->slot; |
| ssize_t cleared = 0; |
| size_t offset = pos->offset; |
| |
| if (WARN_ON_ONCE(!bq)) |
| return 0; |
| |
| if (slot >= bq->nr_slots) { |
| bq = bq->next; |
| if (WARN_ON_ONCE(!bq)) |
| return 0; |
| slot = 0; |
| } |
| |
| do { |
| const struct bio_vec *bv = &bq->bv[slot]; |
| |
| if (offset < bv->bv_len) { |
| size_t part = umin(amount - cleared, bv->bv_len - offset); |
| |
| memzero_page(bv->bv_page, bv->bv_offset + offset, part); |
| |
| offset += part; |
| cleared += part; |
| } |
| |
| if (offset >= bv->bv_len) { |
| offset = 0; |
| slot++; |
| if (slot >= bq->nr_slots) { |
| if (!bq->next) |
| break; |
| bq = bq->next; |
| slot = 0; |
| } |
| } |
| } while (cleared < amount); |
| |
| bvecq_pos_move(pos, bq); |
| pos->slot = slot; |
| pos->offset = offset; |
| return cleared; |
| } |
| |
| /** |
| * bvecq_slice - Find a slice of a bvecq queue |
| * @pos: The position to start at. |
| * @max_size: The maximum size of the slice (or ULONG_MAX). |
| * @max_segs: The maximum number of segments in the slice (or INT_MAX). |
| * @_nr_segs: Where to put the number of segments (updated). |
| * |
| * Determine the size and number of segments that can be obtained the next |
| * slice of bvec queue up to the maximum size and segment count specified. The |
| * slice is also limited if a discontiguity is found. |
| * |
| * @pos is updated to the end of the slice. If the position hits the end of |
| * the queue, then it is left pointing beyond the last slot of the last bvecq |
| * so that it doesn't break the chain. |
| * |
| * Return: The number of bytes in the slice. |
| */ |
| size_t bvecq_slice(struct bvecq_pos *pos, size_t max_size, |
| unsigned int max_segs, unsigned int *_nr_segs) |
| { |
| struct bvecq *bq; |
| unsigned int slot = pos->slot, nsegs = 0; |
| size_t size = 0; |
| size_t offset = pos->offset; |
| |
| bq = pos->bvecq; |
| for (;;) { |
| for (; slot < bq->nr_slots; slot++) { |
| const struct bio_vec *bvec = &bq->bv[slot]; |
| |
| if (offset < bvec->bv_len && bvec->bv_page) { |
| size_t part = umin(bvec->bv_len - offset, max_size); |
| |
| size += part; |
| offset += part; |
| max_size -= part; |
| nsegs++; |
| if (!max_size || nsegs >= max_segs) |
| goto out; |
| } |
| offset = 0; |
| } |
| |
| /* pos->bvecq isn't allowed to go NULL as the queue may get |
| * extended and we would lose our place. |
| */ |
| if (!bq->next) |
| break; |
| slot = 0; |
| bq = bq->next; |
| if (bq->discontig && size > 0) |
| break; |
| } |
| |
| out: |
| *_nr_segs = nsegs; |
| if (slot == bq->nr_slots && bq->next) { |
| bq = bq->next; |
| slot = 0; |
| offset = 0; |
| } |
| bvecq_pos_move(pos, bq); |
| pos->slot = slot; |
| pos->offset = offset; |
| return size; |
| } |
| |
| /** |
| * bvecq_extract - Extract a slice of a bvecq queue into a new bvecq queue |
| * @pos: The position to start at. |
| * @max_size: The maximum size of the slice (or ULONG_MAX). |
| * @max_segs: The maximum number of segments in the slice (or INT_MAX). |
| * @to: Where to put the extraction bvecq chain head (updated). |
| * |
| * Allocate a new bvecq and extract into it memory fragments from a slice of |
| * bvec queue, starting at @pos. The slice is also limited if a discontiguity |
| * is found. No refs are taken on the page. |
| * |
| * @pos is updated to the end of the slice. If the position hits the end of |
| * the queue, then it is left pointing beyond the last slot of the last bvecq |
| * so that it doesn't break the chain. |
| * |
| * If successful, *@to is set to point to the head of the newly allocated chain |
| * and the caller inherits a ref to it. |
| * |
| * Return: The number of bytes extracted; -ENOMEM on allocation failure or -EIO |
| * if no segments were available to extract. |
| */ |
| ssize_t bvecq_extract(struct bvecq_pos *pos, size_t max_size, |
| unsigned int max_segs, struct bvecq **to) |
| { |
| struct bvecq_pos tmp_pos; |
| struct bvecq *src, *dst = NULL; |
| unsigned int slot = pos->slot, nsegs; |
| ssize_t extracted = 0; |
| size_t offset = pos->offset, amount; |
| |
| *to = NULL; |
| if (WARN_ON_ONCE(!max_segs)) |
| max_segs = INT_MAX; |
| |
| bvecq_pos_set(&tmp_pos, pos); |
| amount = bvecq_slice(&tmp_pos, max_size, max_segs, &nsegs); |
| bvecq_pos_unset(&tmp_pos); |
| if (nsegs == 0) |
| return -EIO; |
| |
| dst = bvecq_alloc_chain(nsegs, GFP_KERNEL); |
| if (!dst) |
| return -ENOMEM; |
| *to = dst; |
| max_segs = nsegs; |
| nsegs = 0; |
| |
| /* Transcribe the segments */ |
| src = pos->bvecq; |
| for (;;) { |
| for (; slot < src->nr_slots; slot++) { |
| const struct bio_vec *sv = &src->bv[slot]; |
| struct bio_vec *dv = &dst->bv[dst->nr_slots]; |
| |
| _debug("EXTR BQ=%x[%x] off=%zx am=%zx p=%lx", |
| src->priv, slot, offset, amount, page_to_pfn(sv->bv_page)); |
| |
| if (offset < sv->bv_len && sv->bv_page) { |
| size_t part = umin(sv->bv_len - offset, amount); |
| |
| bvec_set_page(dv, sv->bv_page, part, |
| sv->bv_offset + offset); |
| extracted += part; |
| amount -= part; |
| offset += part; |
| trace_netfs_bv_slot(dst, dst->nr_slots); |
| dst->nr_slots++; |
| nsegs++; |
| if (bvecq_is_full(dst)) |
| dst = dst->next; |
| if (nsegs >= max_segs) |
| goto out; |
| if (amount == 0) |
| goto out; |
| } |
| offset = 0; |
| } |
| |
| /* pos->bvecq isn't allowed to go NULL as the queue may get |
| * extended and we would lose our place. |
| */ |
| if (!src->next) |
| break; |
| slot = 0; |
| src = src->next; |
| if (src->discontig && extracted > 0) |
| break; |
| } |
| |
| out: |
| if (slot == src->nr_slots && src->next) { |
| src = src->next; |
| slot = 0; |
| offset = 0; |
| } |
| bvecq_pos_move(pos, src); |
| pos->slot = slot; |
| pos->offset = offset; |
| return extracted; |
| } |
| |
| /** |
| * bvecq_load_from_ra - Allocate a bvecq chain and load from readahead |
| * @pos: Blank position object to attach the new chain to. |
| * @ractl: The readahead control context. |
| * |
| * Decant the set of folios to be read from the readahead context into a bvecq |
| * chain. Each folio occupies one bio_vec element. |
| * |
| * Return: Amount of data loaded or -ENOMEM on allocation failure. |
| */ |
| ssize_t bvecq_load_from_ra(struct bvecq_pos *pos, struct readahead_control *ractl) |
| { |
| XA_STATE(xas, &ractl->mapping->i_pages, ractl->_index); |
| struct folio *folio; |
| struct bvecq *bq; |
| size_t loaded = 0; |
| |
| bq = bvecq_alloc_chain(ractl->_nr_folios, GFP_NOFS); |
| if (!bq) |
| return -ENOMEM; |
| |
| pos->bvecq = bq; |
| pos->slot = 0; |
| pos->offset = 0; |
| |
| rcu_read_lock(); |
| |
| xas_for_each(&xas, folio, ractl->_index + ractl->_nr_pages - 1) { |
| size_t len; |
| |
| if (xas_retry(&xas, folio)) |
| continue; |
| VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio); |
| |
| len = folio_size(folio); |
| bvec_set_folio(&bq->bv[bq->nr_slots++], folio, len, 0); |
| loaded += len; |
| trace_netfs_folio(folio, netfs_folio_trace_read); |
| |
| if (bq->nr_slots >= bq->max_slots) { |
| bq = bq->next; |
| if (!bq) |
| break; |
| } |
| } |
| |
| rcu_read_unlock(); |
| |
| ractl->_index += ractl->_nr_pages; |
| ractl->_nr_pages = 0; |
| return loaded; |
| } |
| |
| /* |
| * Allocate pages and append them to the specified buffer. |
| */ |
| int bvecq_buffer_add_space(struct bvecq_pos *pos, unsigned long long *start, |
| unsigned long long to, unsigned long long end, |
| bool discontig, gfp_t gfp) |
| { |
| struct bvecq *bq = pos->bvecq, *new_bq = NULL; |
| int orig_nr_slots = bq->nr_slots; |
| |
| while (*start < to) { |
| struct page *page; |
| size_t nr_pages = DIV_ROUND_UP(end - *start, PAGE_SIZE); |
| int order = 0; |
| |
| if (bvecq_is_full(bq) || discontig) { |
| struct bvecq *p; |
| |
| p = bvecq_alloc_one(nr_pages, gfp); |
| if (!p) |
| goto nomem; |
| p->free = true; |
| p->fpos = *start; |
| p->prev = bq; |
| p->discontig = discontig; |
| discontig = false; |
| |
| /* We don't attach new bvecqs to the chain until we've |
| * avoided getting ENOMEM. |
| */ |
| if (!new_bq) |
| new_bq = p; |
| else |
| bq->next = p; |
| bq = p; |
| } |
| |
| if (nr_pages > 1) |
| order = umin(ilog2(nr_pages), MAX_PAGECACHE_ORDER); |
| |
| page = alloc_pages(gfp | __GFP_COMP, order); |
| if (!page && order > 0) { |
| order = 0; |
| if (gfp == GFP_KERNEL) |
| page = alloc_pages(gfp | __GFP_COMP, order); |
| else |
| page = mempool_alloc(&netfs_page_pool, gfp | __GFP_COMP); |
| } |
| if (!page) |
| goto nomem; |
| |
| bvec_set_page(&bq->bv[bq->nr_slots++], page, PAGE_SIZE << order, 0); |
| *start += PAGE_SIZE << order; |
| } |
| |
| if (new_bq) |
| pos->bvecq->next = new_bq; |
| pos->slot = bq->nr_slots; |
| bvecq_pos_move(pos, bq); |
| return 0; |
| |
| nomem: |
| /* Free the pages we just allocated. */ |
| bq = pos->bvecq; |
| for (int i = orig_nr_slots; i < bq->nr_slots; i++) |
| bvecq_free_seg(bq, i); |
| bvecq_put(new_bq); |
| return -ENOMEM; |
| } |
| |
| /* |
| * Copy data from a bvecq chain into a destination iterator. |
| */ |
| int bvecq_copy_to_iter(struct bvecq_pos *pos, struct iov_iter *dst, size_t amount) |
| { |
| struct bvecq *bq; |
| size_t offset = pos->offset; |
| size_t copied = 0; |
| int slot = pos->slot; |
| |
| amount = umin(amount, iov_iter_count(dst)); |
| |
| for (bq = pos->bvecq; bq; bq = bq->next) { |
| for (; slot < bq->nr_slots; slot++) { |
| struct bio_vec *bv = &bq->bv[slot]; |
| size_t part = umin(amount - copied, bv->bv_len - offset); |
| size_t did; |
| |
| if (part) { |
| did = copy_page_to_iter(bv->bv_page, bv->bv_offset + offset, |
| part, dst); |
| if (!did) { |
| if (!copied) |
| return -EFAULT; |
| break; |
| } |
| copied += did; |
| if (copied < part || copied >= amount) |
| break; |
| } |
| offset = 0; |
| } |
| if (!bq->next) |
| break; |
| slot = 0; |
| } |
| |
| pos->offset = offset; |
| pos->slot = slot; |
| bvecq_pos_move(pos, bq); |
| return amount; |
| } |
| |
| static size_t copy_bvec_to_iter(const struct bio_vec *bv, size_t offset, size_t len, |
| struct iov_iter *iter) |
| { |
| return copy_page_to_iter(bv->bv_page, bv->bv_offset + offset, len, iter); |
| } |
| |
| /* |
| * Copy data from the source bvecq chain into the destination bvecq chain. |
| */ |
| int bvecq_copy_to_bvecq(struct bvecq_pos *pos, struct bvecq_pos *to, size_t amount) |
| { |
| struct iov_iter iter; |
| struct bvecq *bq; |
| size_t offset = pos->offset; |
| size_t copied = 0; |
| int slot = pos->slot; |
| |
| iov_iter_bvec_queue(&iter, ITER_DEST, to->bvecq, to->slot, to->offset, amount); |
| |
| for (bq = pos->bvecq; bq; bq = bq->next) { |
| for (; slot < bq->nr_slots; slot++) { |
| struct bio_vec *bv = &bq->bv[slot]; |
| size_t part = umin(amount - copied, bv->bv_len - offset); |
| size_t did; |
| |
| if (part) { |
| did = copy_bvec_to_iter(bv, offset, part, &iter); |
| if (!did) { |
| if (!copied) |
| return -EFAULT; |
| break; |
| } |
| copied += did; |
| if (copied < part || copied >= amount) |
| break; |
| } |
| offset = 0; |
| } |
| if (!bq->next) |
| break; |
| slot = 0; |
| } |
| |
| pos->offset = offset; |
| pos->slot = slot; |
| bvecq_pos_move(pos, bq); |
| to->offset = iter.iov_offset; |
| to->slot = iter.bvecq_slot; |
| bvecq_pos_move(to, (struct bvecq *)iter.bvecq); |
| return amount; |
| } |
| |
| /* |
| * Extract up to sg_max folios from a bvecq and add them to the scatterlist. |
| * The pages are not pinned. |
| */ |
| size_t bvecq_extract_to_sg(struct bvecq_pos *pos, size_t maxsize, |
| struct sg_table *sgtable, unsigned int sg_max) |
| { |
| struct bvecq *bvecq = pos->bvecq; |
| struct scatterlist *sg = sgtable->sgl + sgtable->nents; |
| unsigned int slot = pos->slot; |
| size_t extracted = 0; |
| size_t offset = pos->offset; |
| |
| if (slot >= bvecq->nr_slots) { |
| bvecq = bvecq->next; |
| if (WARN_ON_ONCE(!bvecq)) |
| return 0; |
| slot = 0; |
| } |
| |
| do { |
| const struct bio_vec *bv = &bvecq->bv[slot]; |
| size_t blen = bv->bv_len; |
| |
| if (!bv->bv_page) |
| blen = 0; |
| |
| if (offset < blen) { |
| size_t part = umin(maxsize - extracted, blen - offset); |
| |
| sg_set_page(sg, bv->bv_page, part, bv->bv_offset + offset); |
| sgtable->nents++; |
| sg++; |
| sg_max--; |
| offset += part; |
| extracted += part; |
| } |
| |
| if (offset >= blen) { |
| offset = 0; |
| slot++; |
| if (slot >= bvecq->nr_slots) { |
| if (!bvecq->next) { |
| WARN_ON_ONCE(extracted < maxsize); |
| break; |
| } |
| bvecq = bvecq->next; |
| slot = 0; |
| } |
| } |
| } while (sg_max > 0 && extracted < maxsize); |
| |
| pos->bvecq = bvecq; |
| pos->slot = slot; |
| pos->offset = offset; |
| return extracted; |
| } |
| |
| /* |
| * Add a page to a buffer. |
| */ |
| int bvecq_append_page(struct bvecq_pos *pos, struct page *page, size_t offset, size_t len, |
| gfp_t gfp) |
| { |
| struct bvecq *bq = pos->bvecq; |
| int slot = pos->slot; |
| |
| WARN_ON_ONCE(slot != bq->nr_slots); |
| |
| if (slot >= bq->max_slots || offset > 0) { |
| if (!bq->next && |
| bvecq_buffer_make_space(pos, gfp) < 0) |
| return -ENOMEM; |
| bq = bq->next; |
| if (offset > 0) |
| bq->discontig = true; |
| bvecq_pos_move(pos, bq); |
| slot = 0; |
| } |
| |
| bvec_set_page(&bq->bv[slot++], page, len, offset); |
| bq->nr_slots = slot; |
| pos->slot = slot; |
| return 0; |
| } |