xref: /linux/net/ceph/pagelist.c (revision cc4829e5967de577794b25dfcd1a65e509d171ed)
13d14c5d2SYehuda Sadeh #include <linux/module.h>
23d14c5d2SYehuda Sadeh #include <linux/gfp.h>
33d14c5d2SYehuda Sadeh #include <linux/pagemap.h>
43d14c5d2SYehuda Sadeh #include <linux/highmem.h>
53d14c5d2SYehuda Sadeh #include <linux/ceph/pagelist.h>
63d14c5d2SYehuda Sadeh 
73d14c5d2SYehuda Sadeh static void ceph_pagelist_unmap_tail(struct ceph_pagelist *pl)
83d14c5d2SYehuda Sadeh {
9ac0b74d8SGreg Farnum 	if (pl->mapped_tail) {
10ac0b74d8SGreg Farnum 		struct page *page = list_entry(pl->head.prev, struct page, lru);
113d14c5d2SYehuda Sadeh 		kunmap(page);
12ac0b74d8SGreg Farnum 		pl->mapped_tail = NULL;
13ac0b74d8SGreg Farnum 	}
143d14c5d2SYehuda Sadeh }
153d14c5d2SYehuda Sadeh 
163d14c5d2SYehuda Sadeh int ceph_pagelist_release(struct ceph_pagelist *pl)
173d14c5d2SYehuda Sadeh {
183d14c5d2SYehuda Sadeh 	ceph_pagelist_unmap_tail(pl);
193d14c5d2SYehuda Sadeh 	while (!list_empty(&pl->head)) {
203d14c5d2SYehuda Sadeh 		struct page *page = list_first_entry(&pl->head, struct page,
213d14c5d2SYehuda Sadeh 						     lru);
223d14c5d2SYehuda Sadeh 		list_del(&page->lru);
233d14c5d2SYehuda Sadeh 		__free_page(page);
243d14c5d2SYehuda Sadeh 	}
25ac0b74d8SGreg Farnum 	ceph_pagelist_free_reserve(pl);
263d14c5d2SYehuda Sadeh 	return 0;
273d14c5d2SYehuda Sadeh }
283d14c5d2SYehuda Sadeh EXPORT_SYMBOL(ceph_pagelist_release);
293d14c5d2SYehuda Sadeh 
303d14c5d2SYehuda Sadeh static int ceph_pagelist_addpage(struct ceph_pagelist *pl)
313d14c5d2SYehuda Sadeh {
32ac0b74d8SGreg Farnum 	struct page *page;
33ac0b74d8SGreg Farnum 
34ac0b74d8SGreg Farnum 	if (!pl->num_pages_free) {
35ac0b74d8SGreg Farnum 		page = __page_cache_alloc(GFP_NOFS);
36ac0b74d8SGreg Farnum 	} else {
37ac0b74d8SGreg Farnum 		page = list_first_entry(&pl->free_list, struct page, lru);
38ac0b74d8SGreg Farnum 		list_del(&page->lru);
39240634e9SSage Weil 		--pl->num_pages_free;
40ac0b74d8SGreg Farnum 	}
413d14c5d2SYehuda Sadeh 	if (!page)
423d14c5d2SYehuda Sadeh 		return -ENOMEM;
433d14c5d2SYehuda Sadeh 	pl->room += PAGE_SIZE;
443d14c5d2SYehuda Sadeh 	ceph_pagelist_unmap_tail(pl);
45ac0b74d8SGreg Farnum 	list_add_tail(&page->lru, &pl->head);
463d14c5d2SYehuda Sadeh 	pl->mapped_tail = kmap(page);
473d14c5d2SYehuda Sadeh 	return 0;
483d14c5d2SYehuda Sadeh }
493d14c5d2SYehuda Sadeh 
503d14c5d2SYehuda Sadeh int ceph_pagelist_append(struct ceph_pagelist *pl, const void *buf, size_t len)
513d14c5d2SYehuda Sadeh {
523d14c5d2SYehuda Sadeh 	while (pl->room < len) {
533d14c5d2SYehuda Sadeh 		size_t bit = pl->room;
543d14c5d2SYehuda Sadeh 		int ret;
553d14c5d2SYehuda Sadeh 
563d14c5d2SYehuda Sadeh 		memcpy(pl->mapped_tail + (pl->length & ~PAGE_CACHE_MASK),
573d14c5d2SYehuda Sadeh 		       buf, bit);
583d14c5d2SYehuda Sadeh 		pl->length += bit;
593d14c5d2SYehuda Sadeh 		pl->room -= bit;
603d14c5d2SYehuda Sadeh 		buf += bit;
613d14c5d2SYehuda Sadeh 		len -= bit;
623d14c5d2SYehuda Sadeh 		ret = ceph_pagelist_addpage(pl);
633d14c5d2SYehuda Sadeh 		if (ret)
643d14c5d2SYehuda Sadeh 			return ret;
653d14c5d2SYehuda Sadeh 	}
663d14c5d2SYehuda Sadeh 
673d14c5d2SYehuda Sadeh 	memcpy(pl->mapped_tail + (pl->length & ~PAGE_CACHE_MASK), buf, len);
683d14c5d2SYehuda Sadeh 	pl->length += len;
693d14c5d2SYehuda Sadeh 	pl->room -= len;
703d14c5d2SYehuda Sadeh 	return 0;
713d14c5d2SYehuda Sadeh }
723d14c5d2SYehuda Sadeh EXPORT_SYMBOL(ceph_pagelist_append);
73ac0b74d8SGreg Farnum 
74ae86b9e3SBen Hutchings /* Allocate enough pages for a pagelist to append the given amount
75ac0b74d8SGreg Farnum  * of data without without allocating.
76ac0b74d8SGreg Farnum  * Returns: 0 on success, -ENOMEM on error.
77ac0b74d8SGreg Farnum  */
78ac0b74d8SGreg Farnum int ceph_pagelist_reserve(struct ceph_pagelist *pl, size_t space)
79ac0b74d8SGreg Farnum {
80ac0b74d8SGreg Farnum 	if (space <= pl->room)
81ac0b74d8SGreg Farnum 		return 0;
82ac0b74d8SGreg Farnum 	space -= pl->room;
83ac0b74d8SGreg Farnum 	space = (space + PAGE_SIZE - 1) >> PAGE_SHIFT;   /* conv to num pages */
84ac0b74d8SGreg Farnum 
85ac0b74d8SGreg Farnum 	while (space > pl->num_pages_free) {
86ac0b74d8SGreg Farnum 		struct page *page = __page_cache_alloc(GFP_NOFS);
87ac0b74d8SGreg Farnum 		if (!page)
88ac0b74d8SGreg Farnum 			return -ENOMEM;
89ac0b74d8SGreg Farnum 		list_add_tail(&page->lru, &pl->free_list);
90ac0b74d8SGreg Farnum 		++pl->num_pages_free;
91ac0b74d8SGreg Farnum 	}
92ac0b74d8SGreg Farnum 	return 0;
93ac0b74d8SGreg Farnum }
94ac0b74d8SGreg Farnum EXPORT_SYMBOL(ceph_pagelist_reserve);
95ac0b74d8SGreg Farnum 
96ae86b9e3SBen Hutchings /* Free any pages that have been preallocated. */
97ac0b74d8SGreg Farnum int ceph_pagelist_free_reserve(struct ceph_pagelist *pl)
98ac0b74d8SGreg Farnum {
99ac0b74d8SGreg Farnum 	while (!list_empty(&pl->free_list)) {
100ac0b74d8SGreg Farnum 		struct page *page = list_first_entry(&pl->free_list,
101ac0b74d8SGreg Farnum 						     struct page, lru);
102ac0b74d8SGreg Farnum 		list_del(&page->lru);
103ac0b74d8SGreg Farnum 		__free_page(page);
104ac0b74d8SGreg Farnum 		--pl->num_pages_free;
105ac0b74d8SGreg Farnum 	}
106ac0b74d8SGreg Farnum 	BUG_ON(pl->num_pages_free);
107ac0b74d8SGreg Farnum 	return 0;
108ac0b74d8SGreg Farnum }
109ac0b74d8SGreg Farnum EXPORT_SYMBOL(ceph_pagelist_free_reserve);
110ac0b74d8SGreg Farnum 
111ae86b9e3SBen Hutchings /* Create a truncation point. */
112ac0b74d8SGreg Farnum void ceph_pagelist_set_cursor(struct ceph_pagelist *pl,
113ac0b74d8SGreg Farnum 			      struct ceph_pagelist_cursor *c)
114ac0b74d8SGreg Farnum {
115ac0b74d8SGreg Farnum 	c->pl = pl;
116ac0b74d8SGreg Farnum 	c->page_lru = pl->head.prev;
117ac0b74d8SGreg Farnum 	c->room = pl->room;
118ac0b74d8SGreg Farnum }
119ac0b74d8SGreg Farnum EXPORT_SYMBOL(ceph_pagelist_set_cursor);
120ac0b74d8SGreg Farnum 
121ae86b9e3SBen Hutchings /* Truncate a pagelist to the given point. Move extra pages to reserve.
122ac0b74d8SGreg Farnum  * This won't sleep.
123ac0b74d8SGreg Farnum  * Returns: 0 on success,
124ac0b74d8SGreg Farnum  *          -EINVAL if the pagelist doesn't match the trunc point pagelist
125ac0b74d8SGreg Farnum  */
126ac0b74d8SGreg Farnum int ceph_pagelist_truncate(struct ceph_pagelist *pl,
127ac0b74d8SGreg Farnum 			   struct ceph_pagelist_cursor *c)
128ac0b74d8SGreg Farnum {
129ac0b74d8SGreg Farnum 	struct page *page;
130ac0b74d8SGreg Farnum 
131ac0b74d8SGreg Farnum 	if (pl != c->pl)
132ac0b74d8SGreg Farnum 		return -EINVAL;
133ac0b74d8SGreg Farnum 	ceph_pagelist_unmap_tail(pl);
134ac0b74d8SGreg Farnum 	while (pl->head.prev != c->page_lru) {
135ac0b74d8SGreg Farnum 		page = list_entry(pl->head.prev, struct page, lru);
136*cc4829e5SWei Yongjun 		/* move from pagelist to reserve */
137*cc4829e5SWei Yongjun 		list_move_tail(&page->lru, &pl->free_list);
138ac0b74d8SGreg Farnum 		++pl->num_pages_free;
139ac0b74d8SGreg Farnum 	}
140ac0b74d8SGreg Farnum 	pl->room = c->room;
141ac0b74d8SGreg Farnum 	if (!list_empty(&pl->head)) {
142ac0b74d8SGreg Farnum 		page = list_entry(pl->head.prev, struct page, lru);
143ac0b74d8SGreg Farnum 		pl->mapped_tail = kmap(page);
144ac0b74d8SGreg Farnum 	}
145ac0b74d8SGreg Farnum 	return 0;
146ac0b74d8SGreg Farnum }
147ac0b74d8SGreg Farnum EXPORT_SYMBOL(ceph_pagelist_truncate);
148