Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0
2 : : /*
3 : : * Bad block management
4 : : *
5 : : * - Heavily based on MD badblocks code from Neil Brown
6 : : *
7 : : * Copyright (c) 2015, Intel Corporation.
8 : : */
9 : :
10 : : #include <linux/badblocks.h>
11 : : #include <linux/seqlock.h>
12 : : #include <linux/device.h>
13 : : #include <linux/kernel.h>
14 : : #include <linux/module.h>
15 : : #include <linux/stddef.h>
16 : : #include <linux/types.h>
17 : : #include <linux/slab.h>
18 : :
19 : : /**
20 : : * badblocks_check() - check a given range for bad sectors
21 : : * @bb: the badblocks structure that holds all badblock information
22 : : * @s: sector (start) at which to check for badblocks
23 : : * @sectors: number of sectors to check for badblocks
24 : : * @first_bad: pointer to store location of the first badblock
25 : : * @bad_sectors: pointer to store number of badblocks after @first_bad
26 : : *
27 : : * We can record which blocks on each device are 'bad' and so just
28 : : * fail those blocks, or that stripe, rather than the whole device.
29 : : * Entries in the bad-block table are 64bits wide. This comprises:
30 : : * Length of bad-range, in sectors: 0-511 for lengths 1-512
31 : : * Start of bad-range, sector offset, 54 bits (allows 8 exbibytes)
32 : : * A 'shift' can be set so that larger blocks are tracked and
33 : : * consequently larger devices can be covered.
34 : : * 'Acknowledged' flag - 1 bit. - the most significant bit.
35 : : *
36 : : * Locking of the bad-block table uses a seqlock so badblocks_check
37 : : * might need to retry if it is very unlucky.
38 : : * We will sometimes want to check for bad blocks in a bi_end_io function,
39 : : * so we use the write_seqlock_irq variant.
40 : : *
41 : : * When looking for a bad block we specify a range and want to
42 : : * know if any block in the range is bad. So we binary-search
43 : : * to the last range that starts at-or-before the given endpoint,
44 : : * (or "before the sector after the target range")
45 : : * then see if it ends after the given start.
46 : : *
47 : : * Return:
48 : : * 0: there are no known bad blocks in the range
49 : : * 1: there are known bad block which are all acknowledged
50 : : * -1: there are bad blocks which have not yet been acknowledged in metadata.
51 : : * plus the start/length of the first bad section we overlap.
52 : : */
53 : 0 : int badblocks_check(struct badblocks *bb, sector_t s, int sectors,
54 : : sector_t *first_bad, int *bad_sectors)
55 : : {
56 : 0 : int hi;
57 : 0 : int lo;
58 : 0 : u64 *p = bb->page;
59 : 0 : int rv;
60 : 0 : sector_t target = s + sectors;
61 : 0 : unsigned seq;
62 : :
63 [ # # ]: 0 : if (bb->shift > 0) {
64 : : /* round the start down, and the end up */
65 : 0 : s >>= bb->shift;
66 : 0 : target += (1<<bb->shift) - 1;
67 : 0 : target >>= bb->shift;
68 : 0 : sectors = target - s;
69 : : }
70 : : /* 'target' is now the first block after the bad range */
71 : :
72 : 0 : retry:
73 : 0 : seq = read_seqbegin(&bb->lock);
74 : 0 : lo = 0;
75 : 0 : rv = 0;
76 : 0 : hi = bb->count;
77 : :
78 : : /* Binary search between lo and hi for 'target'
79 : : * i.e. for the last range that starts before 'target'
80 : : */
81 : : /* INVARIANT: ranges before 'lo' and at-or-after 'hi'
82 : : * are known not to be the last range before target.
83 : : * VARIANT: hi-lo is the number of possible
84 : : * ranges, and decreases until it reaches 1
85 : : */
86 [ # # ]: 0 : while (hi - lo > 1) {
87 : 0 : int mid = (lo + hi) / 2;
88 : 0 : sector_t a = BB_OFFSET(p[mid]);
89 : :
90 [ # # ]: 0 : if (a < target)
91 : : /* This could still be the one, earlier ranges
92 : : * could not.
93 : : */
94 : : lo = mid;
95 : : else
96 : : /* This and later ranges are definitely out. */
97 : : hi = mid;
98 : : }
99 : : /* 'lo' might be the last that started before target, but 'hi' isn't */
100 [ # # ]: 0 : if (hi > lo) {
101 : : /* need to check all range that end after 's' to see if
102 : : * any are unacknowledged.
103 : : */
104 [ # # ]: 0 : while (lo >= 0 &&
105 [ # # ]: 0 : BB_OFFSET(p[lo]) + BB_LEN(p[lo]) > s) {
106 [ # # ]: 0 : if (BB_OFFSET(p[lo]) < target) {
107 : : /* starts before the end, and finishes after
108 : : * the start, so they must overlap
109 : : */
110 [ # # # # ]: 0 : if (rv != -1 && BB_ACK(p[lo]))
111 : : rv = 1;
112 : : else
113 : 0 : rv = -1;
114 : 0 : *first_bad = BB_OFFSET(p[lo]);
115 : 0 : *bad_sectors = BB_LEN(p[lo]);
116 : : }
117 : 0 : lo--;
118 : : }
119 : : }
120 : :
121 [ # # ]: 0 : if (read_seqretry(&bb->lock, seq))
122 : 0 : goto retry;
123 : :
124 : 0 : return rv;
125 : : }
126 : : EXPORT_SYMBOL_GPL(badblocks_check);
127 : :
128 : 0 : static void badblocks_update_acked(struct badblocks *bb)
129 : : {
130 : 0 : u64 *p = bb->page;
131 : 0 : int i;
132 : 0 : bool unacked = false;
133 : :
134 : 0 : if (!bb->unacked_exist)
135 : : return;
136 : :
137 [ # # # # ]: 0 : for (i = 0; i < bb->count ; i++) {
138 [ # # # # ]: 0 : if (!BB_ACK(p[i])) {
139 : : unacked = true;
140 : : break;
141 : : }
142 : : }
143 : :
144 [ # # # # ]: 0 : if (!unacked)
145 : 0 : bb->unacked_exist = 0;
146 : : }
147 : :
148 : : /**
149 : : * badblocks_set() - Add a range of bad blocks to the table.
150 : : * @bb: the badblocks structure that holds all badblock information
151 : : * @s: first sector to mark as bad
152 : : * @sectors: number of sectors to mark as bad
153 : : * @acknowledged: weather to mark the bad sectors as acknowledged
154 : : *
155 : : * This might extend the table, or might contract it if two adjacent ranges
156 : : * can be merged. We binary-search to find the 'insertion' point, then
157 : : * decide how best to handle it.
158 : : *
159 : : * Return:
160 : : * 0: success
161 : : * 1: failed to set badblocks (out of space)
162 : : */
163 : 0 : int badblocks_set(struct badblocks *bb, sector_t s, int sectors,
164 : : int acknowledged)
165 : : {
166 : 0 : u64 *p;
167 : 0 : int lo, hi;
168 : 0 : int rv = 0;
169 : 0 : unsigned long flags;
170 : :
171 [ # # ]: 0 : if (bb->shift < 0)
172 : : /* badblocks are disabled */
173 : : return 1;
174 : :
175 [ # # ]: 0 : if (bb->shift) {
176 : : /* round the start down, and the end up */
177 : 0 : sector_t next = s + sectors;
178 : :
179 : 0 : s >>= bb->shift;
180 : 0 : next += (1<<bb->shift) - 1;
181 : 0 : next >>= bb->shift;
182 : 0 : sectors = next - s;
183 : : }
184 : :
185 : 0 : write_seqlock_irqsave(&bb->lock, flags);
186 : :
187 : 0 : p = bb->page;
188 : 0 : lo = 0;
189 : 0 : hi = bb->count;
190 : : /* Find the last range that starts at-or-before 's' */
191 [ # # ]: 0 : while (hi - lo > 1) {
192 : 0 : int mid = (lo + hi) / 2;
193 : 0 : sector_t a = BB_OFFSET(p[mid]);
194 : :
195 [ # # ]: 0 : if (a <= s)
196 : : lo = mid;
197 : : else
198 : : hi = mid;
199 : : }
200 [ # # # # ]: 0 : if (hi > lo && BB_OFFSET(p[lo]) > s)
201 : 0 : hi = lo;
202 : :
203 [ # # ]: 0 : if (hi > lo) {
204 : : /* we found a range that might merge with the start
205 : : * of our new range
206 : : */
207 : 0 : sector_t a = BB_OFFSET(p[lo]);
208 : 0 : sector_t e = a + BB_LEN(p[lo]);
209 : 0 : int ack = BB_ACK(p[lo]);
210 : :
211 [ # # ]: 0 : if (e >= s) {
212 : : /* Yes, we can merge with a previous range */
213 [ # # # # ]: 0 : if (s == a && s + sectors >= e)
214 : : /* new range covers old */
215 : : ack = acknowledged;
216 : : else
217 : 0 : ack = ack && acknowledged;
218 : :
219 : 0 : if (e < s + sectors)
220 : : e = s + sectors;
221 [ # # ]: 0 : if (e - a <= BB_MAX_LEN) {
222 [ # # ]: 0 : p[lo] = BB_MAKE(a, e-a, ack);
223 : 0 : s = e;
224 : : } else {
225 : : /* does not all fit in one range,
226 : : * make p[lo] maximal
227 : : */
228 [ # # ]: 0 : if (BB_LEN(p[lo]) != BB_MAX_LEN)
229 [ # # ]: 0 : p[lo] = BB_MAKE(a, BB_MAX_LEN, ack);
230 : 0 : s = a + BB_MAX_LEN;
231 : : }
232 : 0 : sectors = e - s;
233 : : }
234 : : }
235 [ # # # # ]: 0 : if (sectors && hi < bb->count) {
236 : : /* 'hi' points to the first range that starts after 's'.
237 : : * Maybe we can merge with the start of that range
238 : : */
239 : 0 : sector_t a = BB_OFFSET(p[hi]);
240 : 0 : sector_t e = a + BB_LEN(p[hi]);
241 : 0 : int ack = BB_ACK(p[hi]);
242 : :
243 [ # # ]: 0 : if (a <= s + sectors) {
244 : : /* merging is possible */
245 [ # # ]: 0 : if (e <= s + sectors) {
246 : : /* full overlap */
247 : : e = s + sectors;
248 : : ack = acknowledged;
249 : : } else
250 : 0 : ack = ack && acknowledged;
251 : :
252 : 0 : a = s;
253 [ # # ]: 0 : if (e - a <= BB_MAX_LEN) {
254 [ # # ]: 0 : p[hi] = BB_MAKE(a, e-a, ack);
255 : 0 : s = e;
256 : : } else {
257 [ # # ]: 0 : p[hi] = BB_MAKE(a, BB_MAX_LEN, ack);
258 : 0 : s = a + BB_MAX_LEN;
259 : : }
260 : 0 : sectors = e - s;
261 : 0 : lo = hi;
262 : 0 : hi++;
263 : : }
264 : : }
265 [ # # # # ]: 0 : if (sectors == 0 && hi < bb->count) {
266 : : /* we might be able to combine lo and hi */
267 : : /* Note: 's' is at the end of 'lo' */
268 : 0 : sector_t a = BB_OFFSET(p[hi]);
269 : 0 : int lolen = BB_LEN(p[lo]);
270 : 0 : int hilen = BB_LEN(p[hi]);
271 : 0 : int newlen = lolen + hilen - (s - a);
272 : :
273 [ # # ]: 0 : if (s >= a && newlen < BB_MAX_LEN) {
274 : : /* yes, we can combine them */
275 [ # # # # ]: 0 : int ack = BB_ACK(p[lo]) && BB_ACK(p[hi]);
276 : :
277 [ # # ]: 0 : p[lo] = BB_MAKE(BB_OFFSET(p[lo]), newlen, ack);
278 : 0 : memmove(p + hi, p + hi + 1,
279 : 0 : (bb->count - hi - 1) * 8);
280 : 0 : bb->count--;
281 : : }
282 : : }
283 [ # # ]: 0 : while (sectors) {
284 : : /* didn't merge (it all).
285 : : * Need to add a range just before 'hi'
286 : : */
287 [ # # ]: 0 : if (bb->count >= MAX_BADBLOCKS) {
288 : : /* No room for more */
289 : : rv = 1;
290 : : break;
291 : : } else {
292 : 0 : int this_sectors = sectors;
293 : :
294 : 0 : memmove(p + hi + 1, p + hi,
295 : 0 : (bb->count - hi) * 8);
296 : 0 : bb->count++;
297 : :
298 : 0 : if (this_sectors > BB_MAX_LEN)
299 : : this_sectors = BB_MAX_LEN;
300 [ # # ]: 0 : p[hi] = BB_MAKE(s, this_sectors, acknowledged);
301 : 0 : sectors -= this_sectors;
302 : 0 : s += this_sectors;
303 : : }
304 : : }
305 : :
306 : 0 : bb->changed = 1;
307 [ # # ]: 0 : if (!acknowledged)
308 : 0 : bb->unacked_exist = 1;
309 : : else
310 [ # # ]: 0 : badblocks_update_acked(bb);
311 : 0 : write_sequnlock_irqrestore(&bb->lock, flags);
312 : :
313 : 0 : return rv;
314 : : }
315 : : EXPORT_SYMBOL_GPL(badblocks_set);
316 : :
317 : : /**
318 : : * badblocks_clear() - Remove a range of bad blocks to the table.
319 : : * @bb: the badblocks structure that holds all badblock information
320 : : * @s: first sector to mark as bad
321 : : * @sectors: number of sectors to mark as bad
322 : : *
323 : : * This may involve extending the table if we spilt a region,
324 : : * but it must not fail. So if the table becomes full, we just
325 : : * drop the remove request.
326 : : *
327 : : * Return:
328 : : * 0: success
329 : : * 1: failed to clear badblocks
330 : : */
331 : 0 : int badblocks_clear(struct badblocks *bb, sector_t s, int sectors)
332 : : {
333 : 0 : u64 *p;
334 : 0 : int lo, hi;
335 : 0 : sector_t target = s + sectors;
336 : 0 : int rv = 0;
337 : :
338 [ # # ]: 0 : if (bb->shift > 0) {
339 : : /* When clearing we round the start up and the end down.
340 : : * This should not matter as the shift should align with
341 : : * the block size and no rounding should ever be needed.
342 : : * However it is better the think a block is bad when it
343 : : * isn't than to think a block is not bad when it is.
344 : : */
345 : 0 : s += (1<<bb->shift) - 1;
346 : 0 : s >>= bb->shift;
347 : 0 : target >>= bb->shift;
348 : 0 : sectors = target - s;
349 : : }
350 : :
351 : 0 : write_seqlock_irq(&bb->lock);
352 : :
353 : 0 : p = bb->page;
354 : 0 : lo = 0;
355 : 0 : hi = bb->count;
356 : : /* Find the last range that starts before 'target' */
357 [ # # ]: 0 : while (hi - lo > 1) {
358 : 0 : int mid = (lo + hi) / 2;
359 : 0 : sector_t a = BB_OFFSET(p[mid]);
360 : :
361 [ # # ]: 0 : if (a < target)
362 : : lo = mid;
363 : : else
364 : : hi = mid;
365 : : }
366 [ # # ]: 0 : if (hi > lo) {
367 : : /* p[lo] is the last range that could overlap the
368 : : * current range. Earlier ranges could also overlap,
369 : : * but only this one can overlap the end of the range.
370 : : */
371 [ # # # # ]: 0 : if ((BB_OFFSET(p[lo]) + BB_LEN(p[lo]) > target) &&
372 : : (BB_OFFSET(p[lo]) < target)) {
373 : : /* Partial overlap, leave the tail of this range */
374 : 0 : int ack = BB_ACK(p[lo]);
375 : 0 : sector_t a = BB_OFFSET(p[lo]);
376 : 0 : sector_t end = a + BB_LEN(p[lo]);
377 : :
378 [ # # ]: 0 : if (a < s) {
379 : : /* we need to split this range */
380 [ # # ]: 0 : if (bb->count >= MAX_BADBLOCKS) {
381 : 0 : rv = -ENOSPC;
382 : 0 : goto out;
383 : : }
384 : 0 : memmove(p+lo+1, p+lo, (bb->count - lo) * 8);
385 : 0 : bb->count++;
386 [ # # ]: 0 : p[lo] = BB_MAKE(a, s-a, ack);
387 : 0 : lo++;
388 : : }
389 [ # # ]: 0 : p[lo] = BB_MAKE(target, end - target, ack);
390 : : /* there is no longer an overlap */
391 : 0 : hi = lo;
392 : 0 : lo--;
393 : : }
394 [ # # ]: 0 : while (lo >= 0 &&
395 [ # # # # ]: 0 : (BB_OFFSET(p[lo]) + BB_LEN(p[lo]) > s) &&
396 : : (BB_OFFSET(p[lo]) < target)) {
397 : : /* This range does overlap */
398 [ # # ]: 0 : if (BB_OFFSET(p[lo]) < s) {
399 : : /* Keep the early parts of this range. */
400 : 0 : int ack = BB_ACK(p[lo]);
401 : 0 : sector_t start = BB_OFFSET(p[lo]);
402 : :
403 [ # # ]: 0 : p[lo] = BB_MAKE(start, s - start, ack);
404 : : /* now low doesn't overlap, so.. */
405 : 0 : break;
406 : : }
407 : 0 : lo--;
408 : : }
409 : : /* 'lo' is strictly before, 'hi' is strictly after,
410 : : * anything between needs to be discarded
411 : : */
412 [ # # ]: 0 : if (hi - lo > 1) {
413 : 0 : memmove(p+lo+1, p+hi, (bb->count - hi) * 8);
414 : 0 : bb->count -= (hi - lo - 1);
415 : : }
416 : : }
417 : :
418 [ # # ]: 0 : badblocks_update_acked(bb);
419 : 0 : bb->changed = 1;
420 : 0 : out:
421 : 0 : write_sequnlock_irq(&bb->lock);
422 : 0 : return rv;
423 : : }
424 : : EXPORT_SYMBOL_GPL(badblocks_clear);
425 : :
426 : : /**
427 : : * ack_all_badblocks() - Acknowledge all bad blocks in a list.
428 : : * @bb: the badblocks structure that holds all badblock information
429 : : *
430 : : * This only succeeds if ->changed is clear. It is used by
431 : : * in-kernel metadata updates
432 : : */
433 : 0 : void ack_all_badblocks(struct badblocks *bb)
434 : : {
435 [ # # # # ]: 0 : if (bb->page == NULL || bb->changed)
436 : : /* no point even trying */
437 : : return;
438 : 0 : write_seqlock_irq(&bb->lock);
439 : :
440 [ # # # # ]: 0 : if (bb->changed == 0 && bb->unacked_exist) {
441 : 0 : u64 *p = bb->page;
442 : 0 : int i;
443 : :
444 [ # # ]: 0 : for (i = 0; i < bb->count ; i++) {
445 [ # # ]: 0 : if (!BB_ACK(p[i])) {
446 : 0 : sector_t start = BB_OFFSET(p[i]);
447 : 0 : int len = BB_LEN(p[i]);
448 : :
449 : 0 : p[i] = BB_MAKE(start, len, 1);
450 : : }
451 : : }
452 : 0 : bb->unacked_exist = 0;
453 : : }
454 : 0 : write_sequnlock_irq(&bb->lock);
455 : : }
456 : : EXPORT_SYMBOL_GPL(ack_all_badblocks);
457 : :
458 : : /**
459 : : * badblocks_show() - sysfs access to bad-blocks list
460 : : * @bb: the badblocks structure that holds all badblock information
461 : : * @page: buffer received from sysfs
462 : : * @unack: weather to show unacknowledged badblocks
463 : : *
464 : : * Return:
465 : : * Length of returned data
466 : : */
467 : 0 : ssize_t badblocks_show(struct badblocks *bb, char *page, int unack)
468 : : {
469 : 0 : size_t len;
470 : 0 : int i;
471 : 0 : u64 *p = bb->page;
472 : 0 : unsigned seq;
473 : :
474 [ # # ]: 0 : if (bb->shift < 0)
475 : : return 0;
476 : :
477 : 0 : retry:
478 : 0 : seq = read_seqbegin(&bb->lock);
479 : :
480 : 0 : len = 0;
481 : 0 : i = 0;
482 : :
483 [ # # # # ]: 0 : while (len < PAGE_SIZE && i < bb->count) {
484 : 0 : sector_t s = BB_OFFSET(p[i]);
485 : 0 : unsigned int length = BB_LEN(p[i]);
486 : 0 : int ack = BB_ACK(p[i]);
487 : :
488 : 0 : i++;
489 : :
490 [ # # ]: 0 : if (unack && ack)
491 : 0 : continue;
492 : :
493 : 0 : len += snprintf(page+len, PAGE_SIZE-len, "%llu %u\n",
494 : : (unsigned long long)s << bb->shift,
495 : 0 : length << bb->shift);
496 : : }
497 [ # # ]: 0 : if (unack && len == 0)
498 : 0 : bb->unacked_exist = 0;
499 : :
500 [ # # ]: 0 : if (read_seqretry(&bb->lock, seq))
501 : 0 : goto retry;
502 : :
503 : 0 : return len;
504 : : }
505 : : EXPORT_SYMBOL_GPL(badblocks_show);
506 : :
507 : : /**
508 : : * badblocks_store() - sysfs access to bad-blocks list
509 : : * @bb: the badblocks structure that holds all badblock information
510 : : * @page: buffer received from sysfs
511 : : * @len: length of data received from sysfs
512 : : * @unack: weather to show unacknowledged badblocks
513 : : *
514 : : * Return:
515 : : * Length of the buffer processed or -ve error.
516 : : */
517 : 0 : ssize_t badblocks_store(struct badblocks *bb, const char *page, size_t len,
518 : : int unack)
519 : : {
520 : 0 : unsigned long long sector;
521 : 0 : int length;
522 : 0 : char newline;
523 : :
524 [ # # # ]: 0 : switch (sscanf(page, "%llu %d%c", §or, &length, &newline)) {
525 : 0 : case 3:
526 [ # # ]: 0 : if (newline != '\n')
527 : : return -EINVAL;
528 : : /* fall through */
529 : : case 2:
530 [ # # ]: 0 : if (length <= 0)
531 : : return -EINVAL;
532 : 0 : break;
533 : : default:
534 : : return -EINVAL;
535 : : }
536 : :
537 [ # # ]: 0 : if (badblocks_set(bb, sector, length, !unack))
538 : : return -ENOSPC;
539 : : else
540 : 0 : return len;
541 : : }
542 : : EXPORT_SYMBOL_GPL(badblocks_store);
543 : :
544 : 0 : static int __badblocks_init(struct device *dev, struct badblocks *bb,
545 : : int enable)
546 : : {
547 : 0 : bb->dev = dev;
548 : 0 : bb->count = 0;
549 [ # # ]: 0 : if (enable)
550 : 0 : bb->shift = 0;
551 : : else
552 : 0 : bb->shift = -1;
553 [ # # ]: 0 : if (dev)
554 : 0 : bb->page = devm_kzalloc(dev, PAGE_SIZE, GFP_KERNEL);
555 : : else
556 : 0 : bb->page = kzalloc(PAGE_SIZE, GFP_KERNEL);
557 [ # # ]: 0 : if (!bb->page) {
558 : 0 : bb->shift = -1;
559 : 0 : return -ENOMEM;
560 : : }
561 : 0 : seqlock_init(&bb->lock);
562 : :
563 : 0 : return 0;
564 : : }
565 : :
566 : : /**
567 : : * badblocks_init() - initialize the badblocks structure
568 : : * @bb: the badblocks structure that holds all badblock information
569 : : * @enable: weather to enable badblocks accounting
570 : : *
571 : : * Return:
572 : : * 0: success
573 : : * -ve errno: on error
574 : : */
575 : 0 : int badblocks_init(struct badblocks *bb, int enable)
576 : : {
577 : 0 : return __badblocks_init(NULL, bb, enable);
578 : : }
579 : : EXPORT_SYMBOL_GPL(badblocks_init);
580 : :
581 : 0 : int devm_init_badblocks(struct device *dev, struct badblocks *bb)
582 : : {
583 [ # # ]: 0 : if (!bb)
584 : : return -EINVAL;
585 : 0 : return __badblocks_init(dev, bb, 1);
586 : : }
587 : : EXPORT_SYMBOL_GPL(devm_init_badblocks);
588 : :
589 : : /**
590 : : * badblocks_exit() - free the badblocks structure
591 : : * @bb: the badblocks structure that holds all badblock information
592 : : */
593 : 0 : void badblocks_exit(struct badblocks *bb)
594 : : {
595 [ # # ]: 0 : if (!bb)
596 : : return;
597 [ # # ]: 0 : if (bb->dev)
598 : 0 : devm_kfree(bb->dev, bb->page);
599 : : else
600 : 0 : kfree(bb->page);
601 : 0 : bb->page = NULL;
602 : : }
603 : : EXPORT_SYMBOL_GPL(badblocks_exit);
|