blob: fd2a2f0e8cbbb40a07fa9367186a1418e20475b3 [file] [log] [blame]
Tom Haynesf54bcf22014-12-11 15:34:59 -05001/*
2 * Common NFS I/O operations for the pnfs file based
3 * layout drivers.
4 *
5 * Copyright (c) 2014, Primary Data, Inc. All rights reserved.
6 *
7 * Tom Haynes <loghyr@primarydata.com>
8 */
9
10#include <linux/nfs_fs.h>
11#include <linux/nfs_page.h>
12
13#include "internal.h"
14#include "pnfs.h"
15
16static void pnfs_generic_fenceme(struct inode *inode,
17 struct pnfs_layout_hdr *lo)
18{
19 if (!test_and_clear_bit(NFS_LAYOUT_RETURN, &lo->plh_flags))
20 return;
21 pnfs_return_layout(inode);
22}
23
24void pnfs_generic_rw_release(void *data)
25{
26 struct nfs_pgio_header *hdr = data;
27 struct pnfs_layout_hdr *lo = hdr->lseg->pls_layout;
28
29 pnfs_generic_fenceme(lo->plh_inode, lo);
30 nfs_put_client(hdr->ds_clp);
31 hdr->mds_ops->rpc_release(data);
32}
33EXPORT_SYMBOL_GPL(pnfs_generic_rw_release);
34
35/* Fake up some data that will cause nfs_commit_release to retry the writes. */
36void pnfs_generic_prepare_to_resend_writes(struct nfs_commit_data *data)
37{
38 struct nfs_page *first = nfs_list_entry(data->pages.next);
39
40 data->task.tk_status = 0;
41 memcpy(&data->verf.verifier, &first->wb_verf,
42 sizeof(data->verf.verifier));
43 data->verf.verifier.data[0]++; /* ensure verifier mismatch */
44}
45EXPORT_SYMBOL_GPL(pnfs_generic_prepare_to_resend_writes);
46
47void pnfs_generic_write_commit_done(struct rpc_task *task, void *data)
48{
49 struct nfs_commit_data *wdata = data;
50
51 /* Note this may cause RPC to be resent */
52 wdata->mds_ops->rpc_call_done(task, data);
53}
54EXPORT_SYMBOL_GPL(pnfs_generic_write_commit_done);
55
56void pnfs_generic_commit_release(void *calldata)
57{
58 struct nfs_commit_data *data = calldata;
59
60 data->completion_ops->completion(data);
61 pnfs_put_lseg(data->lseg);
62 nfs_put_client(data->ds_clp);
63 nfs_commitdata_release(data);
64}
65EXPORT_SYMBOL_GPL(pnfs_generic_commit_release);
66
67/* The generic layer is about to remove the req from the commit list.
68 * If this will make the bucket empty, it will need to put the lseg reference.
Tom Haynes085d1e32014-12-11 13:04:55 -050069 * Note this must be called holding the inode (/cinfo) lock
Tom Haynesf54bcf22014-12-11 15:34:59 -050070 */
71void
72pnfs_generic_clear_request_commit(struct nfs_page *req,
73 struct nfs_commit_info *cinfo)
74{
75 struct pnfs_layout_segment *freeme = NULL;
76
77 if (!test_and_clear_bit(PG_COMMIT_TO_DS, &req->wb_flags))
78 goto out;
79 cinfo->ds->nwritten--;
80 if (list_is_singular(&req->wb_list)) {
81 struct pnfs_commit_bucket *bucket;
82
83 bucket = list_first_entry(&req->wb_list,
84 struct pnfs_commit_bucket,
85 written);
86 freeme = bucket->wlseg;
87 bucket->wlseg = NULL;
88 }
89out:
90 nfs_request_remove_commit_list(req, cinfo);
91 pnfs_put_lseg_locked(freeme);
92}
93EXPORT_SYMBOL_GPL(pnfs_generic_clear_request_commit);
94
95static int
96pnfs_generic_transfer_commit_list(struct list_head *src, struct list_head *dst,
97 struct nfs_commit_info *cinfo, int max)
98{
99 struct nfs_page *req, *tmp;
100 int ret = 0;
101
102 list_for_each_entry_safe(req, tmp, src, wb_list) {
103 if (!nfs_lock_request(req))
104 continue;
105 kref_get(&req->wb_kref);
106 if (cond_resched_lock(cinfo->lock))
107 list_safe_reset_next(req, tmp, wb_list);
108 nfs_request_remove_commit_list(req, cinfo);
109 clear_bit(PG_COMMIT_TO_DS, &req->wb_flags);
110 nfs_list_add_request(req, dst);
111 ret++;
112 if ((ret == max) && !cinfo->dreq)
113 break;
114 }
115 return ret;
116}
117
Tom Haynesf54bcf22014-12-11 15:34:59 -0500118static int
119pnfs_generic_scan_ds_commit_list(struct pnfs_commit_bucket *bucket,
120 struct nfs_commit_info *cinfo,
121 int max)
122{
123 struct list_head *src = &bucket->written;
124 struct list_head *dst = &bucket->committing;
125 int ret;
126
Tom Haynes085d1e32014-12-11 13:04:55 -0500127 lockdep_assert_held(cinfo->lock);
Tom Haynesf54bcf22014-12-11 15:34:59 -0500128 ret = pnfs_generic_transfer_commit_list(src, dst, cinfo, max);
129 if (ret) {
130 cinfo->ds->nwritten -= ret;
131 cinfo->ds->ncommitting += ret;
132 bucket->clseg = bucket->wlseg;
133 if (list_empty(src))
134 bucket->wlseg = NULL;
135 else
136 pnfs_get_lseg(bucket->clseg);
137 }
138 return ret;
139}
140
Tom Haynes085d1e32014-12-11 13:04:55 -0500141/* Move reqs from written to committing lists, returning count
142 * of number moved.
Tom Haynesf54bcf22014-12-11 15:34:59 -0500143 */
144int pnfs_generic_scan_commit_lists(struct nfs_commit_info *cinfo,
145 int max)
146{
147 int i, rv = 0, cnt;
148
Tom Haynes085d1e32014-12-11 13:04:55 -0500149 lockdep_assert_held(cinfo->lock);
Tom Haynesf54bcf22014-12-11 15:34:59 -0500150 for (i = 0; i < cinfo->ds->nbuckets && max != 0; i++) {
151 cnt = pnfs_generic_scan_ds_commit_list(&cinfo->ds->buckets[i],
152 cinfo, max);
153 max -= cnt;
154 rv += cnt;
155 }
156 return rv;
157}
158EXPORT_SYMBOL_GPL(pnfs_generic_scan_commit_lists);
159
Tom Haynes085d1e32014-12-11 13:04:55 -0500160/* Pull everything off the committing lists and dump into @dst. */
Tom Haynesf54bcf22014-12-11 15:34:59 -0500161void pnfs_generic_recover_commit_reqs(struct list_head *dst,
162 struct nfs_commit_info *cinfo)
163{
164 struct pnfs_commit_bucket *b;
165 struct pnfs_layout_segment *freeme;
166 int i;
167
Tom Haynes085d1e32014-12-11 13:04:55 -0500168 lockdep_assert_held(cinfo->lock);
Tom Haynesf54bcf22014-12-11 15:34:59 -0500169restart:
Tom Haynesf54bcf22014-12-11 15:34:59 -0500170 for (i = 0, b = cinfo->ds->buckets; i < cinfo->ds->nbuckets; i++, b++) {
171 if (pnfs_generic_transfer_commit_list(&b->written, dst,
172 cinfo, 0)) {
173 freeme = b->wlseg;
174 b->wlseg = NULL;
175 spin_unlock(cinfo->lock);
176 pnfs_put_lseg(freeme);
Tom Haynes085d1e32014-12-11 13:04:55 -0500177 spin_lock(cinfo->lock);
Tom Haynesf54bcf22014-12-11 15:34:59 -0500178 goto restart;
179 }
180 }
181 cinfo->ds->nwritten = 0;
Tom Haynesf54bcf22014-12-11 15:34:59 -0500182}
183EXPORT_SYMBOL_GPL(pnfs_generic_recover_commit_reqs);
184
185static void pnfs_generic_retry_commit(struct nfs_commit_info *cinfo, int idx)
186{
187 struct pnfs_ds_commit_info *fl_cinfo = cinfo->ds;
188 struct pnfs_commit_bucket *bucket;
189 struct pnfs_layout_segment *freeme;
190 int i;
191
192 for (i = idx; i < fl_cinfo->nbuckets; i++) {
193 bucket = &fl_cinfo->buckets[i];
194 if (list_empty(&bucket->committing))
195 continue;
196 nfs_retry_commit(&bucket->committing, bucket->clseg, cinfo);
197 spin_lock(cinfo->lock);
198 freeme = bucket->clseg;
199 bucket->clseg = NULL;
200 spin_unlock(cinfo->lock);
201 pnfs_put_lseg(freeme);
202 }
203}
204
205static unsigned int
206pnfs_generic_alloc_ds_commits(struct nfs_commit_info *cinfo,
207 struct list_head *list)
208{
209 struct pnfs_ds_commit_info *fl_cinfo;
210 struct pnfs_commit_bucket *bucket;
211 struct nfs_commit_data *data;
212 int i;
213 unsigned int nreq = 0;
214
215 fl_cinfo = cinfo->ds;
216 bucket = fl_cinfo->buckets;
217 for (i = 0; i < fl_cinfo->nbuckets; i++, bucket++) {
218 if (list_empty(&bucket->committing))
219 continue;
220 data = nfs_commitdata_alloc();
221 if (!data)
222 break;
223 data->ds_commit_index = i;
224 spin_lock(cinfo->lock);
225 data->lseg = bucket->clseg;
226 bucket->clseg = NULL;
227 spin_unlock(cinfo->lock);
228 list_add(&data->pages, list);
229 nreq++;
230 }
231
232 /* Clean up on error */
233 pnfs_generic_retry_commit(cinfo, i);
234 return nreq;
235}
236
237/* This follows nfs_commit_list pretty closely */
238int
239pnfs_generic_commit_pagelist(struct inode *inode, struct list_head *mds_pages,
240 int how, struct nfs_commit_info *cinfo,
241 int (*initiate_commit)(struct nfs_commit_data *data,
242 int how))
243{
244 struct nfs_commit_data *data, *tmp;
245 LIST_HEAD(list);
246 unsigned int nreq = 0;
247
248 if (!list_empty(mds_pages)) {
249 data = nfs_commitdata_alloc();
250 if (data != NULL) {
251 data->lseg = NULL;
252 list_add(&data->pages, &list);
253 nreq++;
254 } else {
255 nfs_retry_commit(mds_pages, NULL, cinfo);
256 pnfs_generic_retry_commit(cinfo, 0);
257 cinfo->completion_ops->error_cleanup(NFS_I(inode));
258 return -ENOMEM;
259 }
260 }
261
262 nreq += pnfs_generic_alloc_ds_commits(cinfo, &list);
263
264 if (nreq == 0) {
265 cinfo->completion_ops->error_cleanup(NFS_I(inode));
266 goto out;
267 }
268
269 atomic_add(nreq, &cinfo->mds->rpcs_out);
270
271 list_for_each_entry_safe(data, tmp, &list, pages) {
272 list_del_init(&data->pages);
273 if (!data->lseg) {
274 nfs_init_commit(data, mds_pages, NULL, cinfo);
275 nfs_initiate_commit(NFS_CLIENT(inode), data,
276 data->mds_ops, how, 0);
277 } else {
278 struct pnfs_commit_bucket *buckets;
279
280 buckets = cinfo->ds->buckets;
281 nfs_init_commit(data,
282 &buckets[data->ds_commit_index].committing,
283 data->lseg,
284 cinfo);
285 initiate_commit(data, how);
286 }
287 }
288out:
289 cinfo->ds->ncommitting = 0;
290 return PNFS_ATTEMPTED;
291}
292EXPORT_SYMBOL_GPL(pnfs_generic_commit_pagelist);