linux/net/ceph/pagelist.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2#include <linux/module.h>
   3#include <linux/gfp.h>
   4#include <linux/slab.h>
   5#include <linux/pagemap.h>
   6#include <linux/highmem.h>
   7#include <linux/ceph/pagelist.h>
   8
   9struct ceph_pagelist *ceph_pagelist_alloc(gfp_t gfp_flags)
  10{
  11        struct ceph_pagelist *pl;
  12
  13        pl = kmalloc(sizeof(*pl), gfp_flags);
  14        if (!pl)
  15                return NULL;
  16
  17        INIT_LIST_HEAD(&pl->head);
  18        pl->mapped_tail = NULL;
  19        pl->length = 0;
  20        pl->room = 0;
  21        INIT_LIST_HEAD(&pl->free_list);
  22        pl->num_pages_free = 0;
  23        refcount_set(&pl->refcnt, 1);
  24
  25        return pl;
  26}
  27EXPORT_SYMBOL(ceph_pagelist_alloc);
  28
  29static void ceph_pagelist_unmap_tail(struct ceph_pagelist *pl)
  30{
  31        if (pl->mapped_tail) {
  32                struct page *page = list_entry(pl->head.prev, struct page, lru);
  33                kunmap(page);
  34                pl->mapped_tail = NULL;
  35        }
  36}
  37
  38void ceph_pagelist_release(struct ceph_pagelist *pl)
  39{
  40        if (!refcount_dec_and_test(&pl->refcnt))
  41                return;
  42        ceph_pagelist_unmap_tail(pl);
  43        while (!list_empty(&pl->head)) {
  44                struct page *page = list_first_entry(&pl->head, struct page,
  45                                                     lru);
  46                list_del(&page->lru);
  47                __free_page(page);
  48        }
  49        ceph_pagelist_free_reserve(pl);
  50        kfree(pl);
  51}
  52EXPORT_SYMBOL(ceph_pagelist_release);
  53
  54static int ceph_pagelist_addpage(struct ceph_pagelist *pl)
  55{
  56        struct page *page;
  57
  58        if (!pl->num_pages_free) {
  59                page = __page_cache_alloc(GFP_NOFS);
  60        } else {
  61                page = list_first_entry(&pl->free_list, struct page, lru);
  62                list_del(&page->lru);
  63                --pl->num_pages_free;
  64        }
  65        if (!page)
  66                return -ENOMEM;
  67        pl->room += PAGE_SIZE;
  68        ceph_pagelist_unmap_tail(pl);
  69        list_add_tail(&page->lru, &pl->head);
  70        pl->mapped_tail = kmap(page);
  71        return 0;
  72}
  73
  74int ceph_pagelist_append(struct ceph_pagelist *pl, const void *buf, size_t len)
  75{
  76        while (pl->room < len) {
  77                size_t bit = pl->room;
  78                int ret;
  79
  80                memcpy(pl->mapped_tail + (pl->length & ~PAGE_MASK),
  81                       buf, bit);
  82                pl->length += bit;
  83                pl->room -= bit;
  84                buf += bit;
  85                len -= bit;
  86                ret = ceph_pagelist_addpage(pl);
  87                if (ret)
  88                        return ret;
  89        }
  90
  91        memcpy(pl->mapped_tail + (pl->length & ~PAGE_MASK), buf, len);
  92        pl->length += len;
  93        pl->room -= len;
  94        return 0;
  95}
  96EXPORT_SYMBOL(ceph_pagelist_append);
  97
  98/* Allocate enough pages for a pagelist to append the given amount
  99 * of data without without allocating.
 100 * Returns: 0 on success, -ENOMEM on error.
 101 */
 102int ceph_pagelist_reserve(struct ceph_pagelist *pl, size_t space)
 103{
 104        if (space <= pl->room)
 105                return 0;
 106        space -= pl->room;
 107        space = (space + PAGE_SIZE - 1) >> PAGE_SHIFT;   /* conv to num pages */
 108
 109        while (space > pl->num_pages_free) {
 110                struct page *page = __page_cache_alloc(GFP_NOFS);
 111                if (!page)
 112                        return -ENOMEM;
 113                list_add_tail(&page->lru, &pl->free_list);
 114                ++pl->num_pages_free;
 115        }
 116        return 0;
 117}
 118EXPORT_SYMBOL(ceph_pagelist_reserve);
 119
 120/* Free any pages that have been preallocated. */
 121int ceph_pagelist_free_reserve(struct ceph_pagelist *pl)
 122{
 123        while (!list_empty(&pl->free_list)) {
 124                struct page *page = list_first_entry(&pl->free_list,
 125                                                     struct page, lru);
 126                list_del(&page->lru);
 127                __free_page(page);
 128                --pl->num_pages_free;
 129        }
 130        BUG_ON(pl->num_pages_free);
 131        return 0;
 132}
 133EXPORT_SYMBOL(ceph_pagelist_free_reserve);
 134
 135/* Create a truncation point. */
 136void ceph_pagelist_set_cursor(struct ceph_pagelist *pl,
 137                              struct ceph_pagelist_cursor *c)
 138{
 139        c->pl = pl;
 140        c->page_lru = pl->head.prev;
 141        c->room = pl->room;
 142}
 143EXPORT_SYMBOL(ceph_pagelist_set_cursor);
 144
 145/* Truncate a pagelist to the given point. Move extra pages to reserve.
 146 * This won't sleep.
 147 * Returns: 0 on success,
 148 *          -EINVAL if the pagelist doesn't match the trunc point pagelist
 149 */
 150int ceph_pagelist_truncate(struct ceph_pagelist *pl,
 151                           struct ceph_pagelist_cursor *c)
 152{
 153        struct page *page;
 154
 155        if (pl != c->pl)
 156                return -EINVAL;
 157        ceph_pagelist_unmap_tail(pl);
 158        while (pl->head.prev != c->page_lru) {
 159                page = list_entry(pl->head.prev, struct page, lru);
 160                /* move from pagelist to reserve */
 161                list_move_tail(&page->lru, &pl->free_list);
 162                ++pl->num_pages_free;
 163        }
 164        pl->room = c->room;
 165        if (!list_empty(&pl->head)) {
 166                page = list_entry(pl->head.prev, struct page, lru);
 167                pl->mapped_tail = kmap(page);
 168        }
 169        return 0;
 170}
 171EXPORT_SYMBOL(ceph_pagelist_truncate);
 172