1 | /* |
2 | * Device operations for the pnfs client. |
3 | * |
4 | * Copyright (c) 2002 |
5 | * The Regents of the University of Michigan |
6 | * All Rights Reserved |
7 | * |
8 | * Dean Hildebrand <dhildebz@umich.edu> |
9 | * Garth Goodson <Garth.Goodson@netapp.com> |
10 | * |
11 | * Permission is granted to use, copy, create derivative works, and |
12 | * redistribute this software and such derivative works for any purpose, |
13 | * so long as the name of the University of Michigan is not used in |
14 | * any advertising or publicity pertaining to the use or distribution |
15 | * of this software without specific, written prior authorization. If |
16 | * the above copyright notice or any other identification of the |
17 | * University of Michigan is included in any copy of any portion of |
18 | * this software, then the disclaimer below must also be included. |
19 | * |
20 | * This software is provided as is, without representation or warranty |
21 | * of any kind either express or implied, including without limitation |
22 | * the implied warranties of merchantability, fitness for a particular |
23 | * purpose, or noninfringement. The Regents of the University of |
24 | * Michigan shall not be liable for any damages, including special, |
25 | * indirect, incidental, or consequential damages, with respect to any |
26 | * claim arising out of or in connection with the use of the software, |
27 | * even if it has been or is hereafter advised of the possibility of |
28 | * such damages. |
29 | */ |
30 | |
31 | #include <linux/export.h> |
32 | #include <linux/nfs_fs.h> |
33 | #include "nfs4session.h" |
34 | #include "internal.h" |
35 | #include "pnfs.h" |
36 | |
37 | #include "nfs4trace.h" |
38 | |
39 | #define NFSDBG_FACILITY NFSDBG_PNFS |
40 | |
41 | /* |
42 | * Device ID RCU cache. A device ID is unique per server and layout type. |
43 | */ |
44 | #define NFS4_DEVICE_ID_HASH_BITS 5 |
45 | #define NFS4_DEVICE_ID_HASH_SIZE (1 << NFS4_DEVICE_ID_HASH_BITS) |
46 | #define NFS4_DEVICE_ID_HASH_MASK (NFS4_DEVICE_ID_HASH_SIZE - 1) |
47 | |
48 | |
49 | static struct hlist_head nfs4_deviceid_cache[NFS4_DEVICE_ID_HASH_SIZE]; |
50 | static DEFINE_SPINLOCK(nfs4_deviceid_lock); |
51 | |
52 | #ifdef NFS_DEBUG |
53 | void |
54 | nfs4_print_deviceid(const struct nfs4_deviceid *id) |
55 | { |
56 | u32 *p = (u32 *)id; |
57 | |
58 | dprintk("%s: device id= [%x%x%x%x]\n" , __func__, |
59 | p[0], p[1], p[2], p[3]); |
60 | } |
61 | EXPORT_SYMBOL_GPL(nfs4_print_deviceid); |
62 | #endif |
63 | |
64 | static inline u32 |
65 | nfs4_deviceid_hash(const struct nfs4_deviceid *id) |
66 | { |
67 | unsigned char *cptr = (unsigned char *)id->data; |
68 | unsigned int nbytes = NFS4_DEVICEID4_SIZE; |
69 | u32 x = 0; |
70 | |
71 | while (nbytes--) { |
72 | x *= 37; |
73 | x += *cptr++; |
74 | } |
75 | return x & NFS4_DEVICE_ID_HASH_MASK; |
76 | } |
77 | |
78 | static struct nfs4_deviceid_node * |
79 | _lookup_deviceid(const struct pnfs_layoutdriver_type *ld, |
80 | const struct nfs_client *clp, const struct nfs4_deviceid *id, |
81 | long hash) |
82 | { |
83 | struct nfs4_deviceid_node *d; |
84 | |
85 | hlist_for_each_entry_rcu(d, &nfs4_deviceid_cache[hash], node) |
86 | if (d->ld == ld && d->nfs_client == clp && |
87 | !memcmp(p: &d->deviceid, q: id, size: sizeof(*id))) { |
88 | if (atomic_read(v: &d->ref)) |
89 | return d; |
90 | else |
91 | continue; |
92 | } |
93 | return NULL; |
94 | } |
95 | |
96 | static struct nfs4_deviceid_node * |
97 | nfs4_get_device_info(struct nfs_server *server, |
98 | const struct nfs4_deviceid *dev_id, |
99 | const struct cred *cred, gfp_t gfp_flags) |
100 | { |
101 | struct nfs4_deviceid_node *d = NULL; |
102 | struct pnfs_device *pdev = NULL; |
103 | struct page **pages = NULL; |
104 | u32 max_resp_sz; |
105 | int max_pages; |
106 | int rc, i; |
107 | |
108 | /* |
109 | * Use the session max response size as the basis for setting |
110 | * GETDEVICEINFO's maxcount |
111 | */ |
112 | max_resp_sz = server->nfs_client->cl_session->fc_attrs.max_resp_sz; |
113 | if (server->pnfs_curr_ld->max_deviceinfo_size && |
114 | server->pnfs_curr_ld->max_deviceinfo_size < max_resp_sz) |
115 | max_resp_sz = server->pnfs_curr_ld->max_deviceinfo_size; |
116 | max_pages = nfs_page_array_len(base: 0, len: max_resp_sz); |
117 | dprintk("%s: server %p max_resp_sz %u max_pages %d\n" , |
118 | __func__, server, max_resp_sz, max_pages); |
119 | |
120 | pdev = kzalloc(size: sizeof(*pdev), flags: gfp_flags); |
121 | if (!pdev) |
122 | return NULL; |
123 | |
124 | pages = kcalloc(n: max_pages, size: sizeof(struct page *), flags: gfp_flags); |
125 | if (!pages) |
126 | goto out_free_pdev; |
127 | |
128 | for (i = 0; i < max_pages; i++) { |
129 | pages[i] = alloc_page(gfp_flags); |
130 | if (!pages[i]) |
131 | goto out_free_pages; |
132 | } |
133 | |
134 | memcpy(&pdev->dev_id, dev_id, sizeof(*dev_id)); |
135 | pdev->layout_type = server->pnfs_curr_ld->id; |
136 | pdev->pages = pages; |
137 | pdev->pgbase = 0; |
138 | pdev->pglen = max_resp_sz; |
139 | pdev->mincount = 0; |
140 | pdev->maxcount = max_resp_sz - nfs41_maxgetdevinfo_overhead; |
141 | |
142 | rc = nfs4_proc_getdeviceinfo(server, dev: pdev, cred); |
143 | dprintk("%s getdevice info returns %d\n" , __func__, rc); |
144 | if (rc) |
145 | goto out_free_pages; |
146 | |
147 | /* |
148 | * Found new device, need to decode it and then add it to the |
149 | * list of known devices for this mountpoint. |
150 | */ |
151 | d = server->pnfs_curr_ld->alloc_deviceid_node(server, pdev, |
152 | gfp_flags); |
153 | if (d && pdev->nocache) |
154 | set_bit(nr: NFS_DEVICEID_NOCACHE, addr: &d->flags); |
155 | |
156 | out_free_pages: |
157 | while (--i >= 0) |
158 | __free_page(pages[i]); |
159 | kfree(objp: pages); |
160 | out_free_pdev: |
161 | kfree(objp: pdev); |
162 | dprintk("<-- %s d %p\n" , __func__, d); |
163 | return d; |
164 | } |
165 | |
166 | /* |
167 | * Lookup a deviceid in cache and get a reference count on it if found |
168 | * |
169 | * @clp nfs_client associated with deviceid |
170 | * @id deviceid to look up |
171 | */ |
172 | static struct nfs4_deviceid_node * |
173 | __nfs4_find_get_deviceid(struct nfs_server *server, |
174 | const struct nfs4_deviceid *id, long hash) |
175 | { |
176 | struct nfs4_deviceid_node *d; |
177 | |
178 | rcu_read_lock(); |
179 | d = _lookup_deviceid(ld: server->pnfs_curr_ld, clp: server->nfs_client, id, |
180 | hash); |
181 | if (d != NULL && !atomic_inc_not_zero(v: &d->ref)) |
182 | d = NULL; |
183 | rcu_read_unlock(); |
184 | return d; |
185 | } |
186 | |
187 | struct nfs4_deviceid_node * |
188 | nfs4_find_get_deviceid(struct nfs_server *server, |
189 | const struct nfs4_deviceid *id, const struct cred *cred, |
190 | gfp_t gfp_mask) |
191 | { |
192 | long hash = nfs4_deviceid_hash(id); |
193 | struct nfs4_deviceid_node *d, *new; |
194 | |
195 | d = __nfs4_find_get_deviceid(server, id, hash); |
196 | if (d) |
197 | goto found; |
198 | |
199 | new = nfs4_get_device_info(server, dev_id: id, cred, gfp_flags: gfp_mask); |
200 | if (!new) { |
201 | trace_nfs4_find_deviceid(server, deviceid: id, status: -ENOENT); |
202 | return new; |
203 | } |
204 | |
205 | spin_lock(lock: &nfs4_deviceid_lock); |
206 | d = __nfs4_find_get_deviceid(server, id, hash); |
207 | if (d) { |
208 | spin_unlock(lock: &nfs4_deviceid_lock); |
209 | server->pnfs_curr_ld->free_deviceid_node(new); |
210 | } else { |
211 | atomic_inc(v: &new->ref); |
212 | hlist_add_head_rcu(n: &new->node, h: &nfs4_deviceid_cache[hash]); |
213 | spin_unlock(lock: &nfs4_deviceid_lock); |
214 | d = new; |
215 | } |
216 | found: |
217 | trace_nfs4_find_deviceid(server, deviceid: id, status: 0); |
218 | return d; |
219 | } |
220 | EXPORT_SYMBOL_GPL(nfs4_find_get_deviceid); |
221 | |
222 | /* |
223 | * Remove a deviceid from cache |
224 | * |
225 | * @clp nfs_client associated with deviceid |
226 | * @id the deviceid to unhash |
227 | * |
228 | * @ret the unhashed node, if found and dereferenced to zero, NULL otherwise. |
229 | */ |
230 | void |
231 | nfs4_delete_deviceid(const struct pnfs_layoutdriver_type *ld, |
232 | const struct nfs_client *clp, const struct nfs4_deviceid *id) |
233 | { |
234 | struct nfs4_deviceid_node *d; |
235 | |
236 | spin_lock(lock: &nfs4_deviceid_lock); |
237 | rcu_read_lock(); |
238 | d = _lookup_deviceid(ld, clp, id, hash: nfs4_deviceid_hash(id)); |
239 | rcu_read_unlock(); |
240 | if (!d) { |
241 | spin_unlock(lock: &nfs4_deviceid_lock); |
242 | return; |
243 | } |
244 | hlist_del_init_rcu(n: &d->node); |
245 | clear_bit(nr: NFS_DEVICEID_NOCACHE, addr: &d->flags); |
246 | spin_unlock(lock: &nfs4_deviceid_lock); |
247 | |
248 | /* balance the initial ref set in pnfs_insert_deviceid */ |
249 | nfs4_put_deviceid_node(d); |
250 | } |
251 | EXPORT_SYMBOL_GPL(nfs4_delete_deviceid); |
252 | |
253 | void |
254 | nfs4_init_deviceid_node(struct nfs4_deviceid_node *d, struct nfs_server *server, |
255 | const struct nfs4_deviceid *id) |
256 | { |
257 | INIT_HLIST_NODE(h: &d->node); |
258 | INIT_HLIST_NODE(h: &d->tmpnode); |
259 | d->ld = server->pnfs_curr_ld; |
260 | d->nfs_client = server->nfs_client; |
261 | d->flags = 0; |
262 | d->deviceid = *id; |
263 | atomic_set(v: &d->ref, i: 1); |
264 | } |
265 | EXPORT_SYMBOL_GPL(nfs4_init_deviceid_node); |
266 | |
267 | /* |
268 | * Dereference a deviceid node and delete it when its reference count drops |
269 | * to zero. |
270 | * |
271 | * @d deviceid node to put |
272 | * |
273 | * return true iff the node was deleted |
274 | * Note that since the test for d->ref == 0 is sufficient to establish |
275 | * that the node is no longer hashed in the global device id cache. |
276 | */ |
277 | bool |
278 | nfs4_put_deviceid_node(struct nfs4_deviceid_node *d) |
279 | { |
280 | if (test_bit(NFS_DEVICEID_NOCACHE, &d->flags)) { |
281 | if (atomic_add_unless(v: &d->ref, a: -1, u: 2)) |
282 | return false; |
283 | nfs4_delete_deviceid(d->ld, d->nfs_client, &d->deviceid); |
284 | } |
285 | if (!atomic_dec_and_test(v: &d->ref)) |
286 | return false; |
287 | trace_nfs4_deviceid_free(clp: d->nfs_client, deviceid: &d->deviceid); |
288 | d->ld->free_deviceid_node(d); |
289 | return true; |
290 | } |
291 | EXPORT_SYMBOL_GPL(nfs4_put_deviceid_node); |
292 | |
293 | void |
294 | nfs4_mark_deviceid_available(struct nfs4_deviceid_node *node) |
295 | { |
296 | if (test_bit(NFS_DEVICEID_UNAVAILABLE, &node->flags)) { |
297 | clear_bit(nr: NFS_DEVICEID_UNAVAILABLE, addr: &node->flags); |
298 | smp_mb__after_atomic(); |
299 | } |
300 | } |
301 | EXPORT_SYMBOL_GPL(nfs4_mark_deviceid_available); |
302 | |
303 | void |
304 | nfs4_mark_deviceid_unavailable(struct nfs4_deviceid_node *node) |
305 | { |
306 | node->timestamp_unavailable = jiffies; |
307 | smp_mb__before_atomic(); |
308 | set_bit(nr: NFS_DEVICEID_UNAVAILABLE, addr: &node->flags); |
309 | smp_mb__after_atomic(); |
310 | } |
311 | EXPORT_SYMBOL_GPL(nfs4_mark_deviceid_unavailable); |
312 | |
313 | bool |
314 | nfs4_test_deviceid_unavailable(struct nfs4_deviceid_node *node) |
315 | { |
316 | if (test_bit(NFS_DEVICEID_UNAVAILABLE, &node->flags)) { |
317 | unsigned long start, end; |
318 | |
319 | end = jiffies; |
320 | start = end - PNFS_DEVICE_RETRY_TIMEOUT; |
321 | if (time_in_range(node->timestamp_unavailable, start, end)) |
322 | return true; |
323 | clear_bit(nr: NFS_DEVICEID_UNAVAILABLE, addr: &node->flags); |
324 | smp_mb__after_atomic(); |
325 | } |
326 | return false; |
327 | } |
328 | EXPORT_SYMBOL_GPL(nfs4_test_deviceid_unavailable); |
329 | |
330 | static void |
331 | _deviceid_purge_client(const struct nfs_client *clp, long hash) |
332 | { |
333 | struct nfs4_deviceid_node *d; |
334 | HLIST_HEAD(tmp); |
335 | |
336 | spin_lock(lock: &nfs4_deviceid_lock); |
337 | rcu_read_lock(); |
338 | hlist_for_each_entry_rcu(d, &nfs4_deviceid_cache[hash], node) |
339 | if (d->nfs_client == clp && atomic_read(v: &d->ref)) { |
340 | hlist_del_init_rcu(n: &d->node); |
341 | hlist_add_head(n: &d->tmpnode, h: &tmp); |
342 | clear_bit(nr: NFS_DEVICEID_NOCACHE, addr: &d->flags); |
343 | } |
344 | rcu_read_unlock(); |
345 | spin_unlock(lock: &nfs4_deviceid_lock); |
346 | |
347 | if (hlist_empty(h: &tmp)) |
348 | return; |
349 | |
350 | while (!hlist_empty(h: &tmp)) { |
351 | d = hlist_entry(tmp.first, struct nfs4_deviceid_node, tmpnode); |
352 | hlist_del(n: &d->tmpnode); |
353 | nfs4_put_deviceid_node(d); |
354 | } |
355 | } |
356 | |
357 | void |
358 | nfs4_deviceid_purge_client(const struct nfs_client *clp) |
359 | { |
360 | long h; |
361 | |
362 | if (!(clp->cl_exchange_flags & EXCHGID4_FLAG_USE_PNFS_MDS)) |
363 | return; |
364 | for (h = 0; h < NFS4_DEVICE_ID_HASH_SIZE; h++) |
365 | _deviceid_purge_client(clp, hash: h); |
366 | } |
367 | |
368 | /* |
369 | * Stop use of all deviceids associated with an nfs_client |
370 | */ |
371 | void |
372 | nfs4_deviceid_mark_client_invalid(struct nfs_client *clp) |
373 | { |
374 | struct nfs4_deviceid_node *d; |
375 | int i; |
376 | |
377 | rcu_read_lock(); |
378 | for (i = 0; i < NFS4_DEVICE_ID_HASH_SIZE; i ++){ |
379 | hlist_for_each_entry_rcu(d, &nfs4_deviceid_cache[i], node) |
380 | if (d->nfs_client == clp) |
381 | set_bit(nr: NFS_DEVICEID_INVALID, addr: &d->flags); |
382 | } |
383 | rcu_read_unlock(); |
384 | } |
385 | |