1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2004, 2007 Lukas Ertl 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include <sys/param.h> 33 #include <sys/bio.h> 34 #include <sys/lock.h> 35 #include <sys/malloc.h> 36 #include <sys/systm.h> 37 38 #include <geom/geom.h> 39 #include <geom/geom_dbg.h> 40 #include <geom/vinum/geom_vinum_var.h> 41 #include <geom/vinum/geom_vinum_raid5.h> 42 #include <geom/vinum/geom_vinum.h> 43 44 static int gv_raid5_offset(struct gv_plex *, off_t, off_t, 45 off_t *, off_t *, int *, int *, int); 46 static struct bio * gv_raid5_clone_bio(struct bio *, struct gv_sd *, 47 struct gv_raid5_packet *, caddr_t, int); 48 static int gv_raid5_request(struct gv_plex *, struct gv_raid5_packet *, 49 struct bio *, caddr_t, off_t, off_t, int *); 50 static int gv_raid5_check(struct gv_plex *, struct gv_raid5_packet *, 51 struct bio *, caddr_t, off_t, off_t); 52 static int gv_raid5_rebuild(struct gv_plex *, struct gv_raid5_packet *, 53 struct bio *, caddr_t, off_t, off_t); 54 55 struct gv_raid5_packet * 56 gv_raid5_start(struct gv_plex *p, struct bio *bp, caddr_t addr, off_t boff, 57 off_t bcount) 58 { 59 struct bio *cbp; 60 struct gv_raid5_packet *wp, *wp2; 61 struct gv_bioq *bq, *bq2; 62 int err, delay; 63 64 delay = 0; 65 wp = g_malloc(sizeof(*wp), M_WAITOK | M_ZERO); 66 wp->bio = bp; 67 wp->waiting = NULL; 68 wp->parity = NULL; 69 TAILQ_INIT(&wp->bits); 70 71 if (bp->bio_pflags & GV_BIO_REBUILD) 72 err = gv_raid5_rebuild(p, wp, bp, addr, boff, bcount); 73 else if (bp->bio_pflags & GV_BIO_CHECK) 74 err = gv_raid5_check(p, wp, bp, addr, boff, bcount); 75 else 76 err = gv_raid5_request(p, wp, bp, addr, boff, bcount, &delay); 77 78 /* Means we have a delayed request. */ 79 if (delay) { 80 g_free(wp); 81 return (NULL); 82 } 83 84 /* 85 * Building the sub-request failed, we probably need to clean up a lot. 86 */ 87 if (err) { 88 G_VINUM_LOGREQ(0, bp, "raid5 plex request failed."); 89 TAILQ_FOREACH_SAFE(bq, &wp->bits, queue, bq2) { 90 TAILQ_REMOVE(&wp->bits, bq, queue); 91 g_free(bq); 92 } 93 if (wp->waiting != NULL) { 94 if (wp->waiting->bio_cflags & GV_BIO_MALLOC) 95 g_free(wp->waiting->bio_data); 96 gv_drive_done(wp->waiting->bio_caller1); 97 g_destroy_bio(wp->waiting); 98 } 99 if (wp->parity != NULL) { 100 if (wp->parity->bio_cflags & GV_BIO_MALLOC) 101 g_free(wp->parity->bio_data); 102 gv_drive_done(wp->parity->bio_caller1); 103 g_destroy_bio(wp->parity); 104 } 105 g_free(wp); 106 107 TAILQ_FOREACH_SAFE(wp, &p->packets, list, wp2) { 108 if (wp->bio != bp) 109 continue; 110 111 TAILQ_REMOVE(&p->packets, wp, list); 112 TAILQ_FOREACH_SAFE(bq, &wp->bits, queue, bq2) { 113 TAILQ_REMOVE(&wp->bits, bq, queue); 114 g_free(bq); 115 } 116 g_free(wp); 117 } 118 119 cbp = bioq_takefirst(p->bqueue); 120 while (cbp != NULL) { 121 if (cbp->bio_cflags & GV_BIO_MALLOC) 122 g_free(cbp->bio_data); 123 gv_drive_done(cbp->bio_caller1); 124 g_destroy_bio(cbp); 125 cbp = bioq_takefirst(p->bqueue); 126 } 127 128 /* If internal, stop and reset state. */ 129 if (bp->bio_pflags & GV_BIO_INTERNAL) { 130 if (bp->bio_pflags & GV_BIO_MALLOC) 131 g_free(bp->bio_data); 132 g_destroy_bio(bp); 133 /* Reset flags. */ 134 p->flags &= ~(GV_PLEX_SYNCING | GV_PLEX_REBUILDING | 135 GV_PLEX_GROWING); 136 return (NULL); 137 } 138 g_io_deliver(bp, err); 139 return (NULL); 140 } 141 142 return (wp); 143 } 144 145 /* 146 * Check if the stripe that the work packet wants is already being used by 147 * some other work packet. 148 */ 149 int 150 gv_stripe_active(struct gv_plex *p, struct bio *bp) 151 { 152 struct gv_raid5_packet *wp, *owp; 153 int overlap; 154 155 wp = bp->bio_caller2; 156 if (wp->lockbase == -1) 157 return (0); 158 159 overlap = 0; 160 TAILQ_FOREACH(owp, &p->packets, list) { 161 if (owp == wp) 162 break; 163 if ((wp->lockbase >= owp->lockbase) && 164 (wp->lockbase <= owp->lockbase + owp->length)) { 165 overlap++; 166 break; 167 } 168 if ((wp->lockbase <= owp->lockbase) && 169 (wp->lockbase + wp->length >= owp->lockbase)) { 170 overlap++; 171 break; 172 } 173 } 174 175 return (overlap); 176 } 177 178 static int 179 gv_raid5_check(struct gv_plex *p, struct gv_raid5_packet *wp, struct bio *bp, 180 caddr_t addr, off_t boff, off_t bcount) 181 { 182 struct gv_sd *parity, *s; 183 struct gv_bioq *bq; 184 struct bio *cbp; 185 int i, psdno; 186 off_t real_len, real_off; 187 188 if (p == NULL || LIST_EMPTY(&p->subdisks)) 189 return (ENXIO); 190 191 gv_raid5_offset(p, boff, bcount, &real_off, &real_len, NULL, &psdno, 1); 192 193 /* Find the right subdisk. */ 194 parity = NULL; 195 i = 0; 196 LIST_FOREACH(s, &p->subdisks, in_plex) { 197 if (i == psdno) { 198 parity = s; 199 break; 200 } 201 i++; 202 } 203 204 /* Parity stripe not found. */ 205 if (parity == NULL) 206 return (ENXIO); 207 208 if (parity->state != GV_SD_UP) 209 return (ENXIO); 210 211 wp->length = real_len; 212 wp->data = addr; 213 wp->lockbase = real_off; 214 215 /* Read all subdisks. */ 216 LIST_FOREACH(s, &p->subdisks, in_plex) { 217 /* Skip the parity subdisk. */ 218 if (s == parity) 219 continue; 220 /* Skip growing subdisks. */ 221 if (s->flags & GV_SD_GROW) 222 continue; 223 224 cbp = gv_raid5_clone_bio(bp, s, wp, NULL, 1); 225 if (cbp == NULL) 226 return (ENOMEM); 227 cbp->bio_cmd = BIO_READ; 228 229 bioq_insert_tail(p->bqueue, cbp); 230 231 bq = g_malloc(sizeof(*bq), M_WAITOK | M_ZERO); 232 bq->bp = cbp; 233 TAILQ_INSERT_TAIL(&wp->bits, bq, queue); 234 } 235 236 /* Read the parity data. */ 237 cbp = gv_raid5_clone_bio(bp, parity, wp, NULL, 1); 238 if (cbp == NULL) 239 return (ENOMEM); 240 cbp->bio_cmd = BIO_READ; 241 wp->waiting = cbp; 242 243 /* 244 * In case we want to rebuild the parity, create an extra BIO to write 245 * it out. It also acts as buffer for the XOR operations. 246 */ 247 cbp = gv_raid5_clone_bio(bp, parity, wp, addr, 1); 248 if (cbp == NULL) 249 return (ENOMEM); 250 wp->parity = cbp; 251 252 return (0); 253 } 254 255 /* Rebuild a degraded RAID5 plex. */ 256 static int 257 gv_raid5_rebuild(struct gv_plex *p, struct gv_raid5_packet *wp, struct bio *bp, 258 caddr_t addr, off_t boff, off_t bcount) 259 { 260 struct gv_sd *broken, *s; 261 struct gv_bioq *bq; 262 struct bio *cbp; 263 off_t real_len, real_off; 264 265 if (p == NULL || LIST_EMPTY(&p->subdisks)) 266 return (ENXIO); 267 268 gv_raid5_offset(p, boff, bcount, &real_off, &real_len, NULL, NULL, 1); 269 270 /* Find the right subdisk. */ 271 broken = NULL; 272 LIST_FOREACH(s, &p->subdisks, in_plex) { 273 if (s->state != GV_SD_UP) 274 broken = s; 275 } 276 277 /* Broken stripe not found. */ 278 if (broken == NULL) 279 return (ENXIO); 280 281 switch (broken->state) { 282 case GV_SD_UP: 283 return (EINVAL); 284 285 case GV_SD_STALE: 286 if (!(bp->bio_pflags & GV_BIO_REBUILD)) 287 return (ENXIO); 288 289 G_VINUM_DEBUG(1, "sd %s is reviving", broken->name); 290 gv_set_sd_state(broken, GV_SD_REVIVING, GV_SETSTATE_FORCE); 291 /* Set this bit now, but should be set at end. */ 292 broken->flags |= GV_SD_CANGOUP; 293 break; 294 295 case GV_SD_REVIVING: 296 break; 297 298 default: 299 /* All other subdisk states mean it's not accessible. */ 300 return (ENXIO); 301 } 302 303 wp->length = real_len; 304 wp->data = addr; 305 wp->lockbase = real_off; 306 307 KASSERT(wp->length >= 0, ("gv_rebuild_raid5: wp->length < 0")); 308 309 /* Read all subdisks. */ 310 LIST_FOREACH(s, &p->subdisks, in_plex) { 311 /* Skip the broken subdisk. */ 312 if (s == broken) 313 continue; 314 315 /* Skip growing subdisks. */ 316 if (s->flags & GV_SD_GROW) 317 continue; 318 319 cbp = gv_raid5_clone_bio(bp, s, wp, NULL, 1); 320 if (cbp == NULL) 321 return (ENOMEM); 322 cbp->bio_cmd = BIO_READ; 323 324 bioq_insert_tail(p->bqueue, cbp); 325 326 bq = g_malloc(sizeof(*bq), M_WAITOK | M_ZERO); 327 bq->bp = cbp; 328 TAILQ_INSERT_TAIL(&wp->bits, bq, queue); 329 } 330 331 /* Write the parity data. */ 332 cbp = gv_raid5_clone_bio(bp, broken, wp, NULL, 1); 333 if (cbp == NULL) 334 return (ENOMEM); 335 wp->parity = cbp; 336 337 p->synced = boff; 338 339 /* Post notification that we're finished. */ 340 return (0); 341 } 342 343 /* Build a request group to perform (part of) a RAID5 request. */ 344 static int 345 gv_raid5_request(struct gv_plex *p, struct gv_raid5_packet *wp, 346 struct bio *bp, caddr_t addr, off_t boff, off_t bcount, int *delay) 347 { 348 struct g_geom *gp; 349 struct gv_sd *broken, *original, *parity, *s; 350 struct gv_bioq *bq; 351 struct bio *cbp; 352 int i, psdno, sdno, type, grow; 353 off_t real_len, real_off; 354 355 gp = bp->bio_to->geom; 356 357 if (p == NULL || LIST_EMPTY(&p->subdisks)) 358 return (ENXIO); 359 360 /* We are optimistic and assume that this request will be OK. */ 361 #define REQ_TYPE_NORMAL 0 362 #define REQ_TYPE_DEGRADED 1 363 #define REQ_TYPE_NOPARITY 2 364 365 type = REQ_TYPE_NORMAL; 366 original = parity = broken = NULL; 367 368 /* XXX: The resize won't crash with rebuild or sync, but we should still 369 * be aware of it. Also this should perhaps be done on rebuild/check as 370 * well? 371 */ 372 /* If we're over, we must use the old. */ 373 if (boff >= p->synced) { 374 grow = 1; 375 /* Or if over the resized offset, we use all drives. */ 376 } else if (boff + bcount <= p->synced) { 377 grow = 0; 378 /* Else, we're in the middle, and must wait a bit. */ 379 } else { 380 bioq_disksort(p->rqueue, bp); 381 *delay = 1; 382 return (0); 383 } 384 gv_raid5_offset(p, boff, bcount, &real_off, &real_len, 385 &sdno, &psdno, grow); 386 387 /* Find the right subdisks. */ 388 i = 0; 389 LIST_FOREACH(s, &p->subdisks, in_plex) { 390 if (i == sdno) 391 original = s; 392 if (i == psdno) 393 parity = s; 394 if (s->state != GV_SD_UP) 395 broken = s; 396 i++; 397 } 398 399 if ((original == NULL) || (parity == NULL)) 400 return (ENXIO); 401 402 /* Our data stripe is missing. */ 403 if (original->state != GV_SD_UP) 404 type = REQ_TYPE_DEGRADED; 405 406 /* If synchronizing request, just write it if disks are stale. */ 407 if (original->state == GV_SD_STALE && parity->state == GV_SD_STALE && 408 bp->bio_pflags & GV_BIO_SYNCREQ && bp->bio_cmd == BIO_WRITE) { 409 type = REQ_TYPE_NORMAL; 410 /* Our parity stripe is missing. */ 411 } else if (parity->state != GV_SD_UP) { 412 /* We cannot take another failure if we're already degraded. */ 413 if (type != REQ_TYPE_NORMAL) 414 return (ENXIO); 415 else 416 type = REQ_TYPE_NOPARITY; 417 } 418 419 wp->length = real_len; 420 wp->data = addr; 421 wp->lockbase = real_off; 422 423 KASSERT(wp->length >= 0, ("gv_build_raid5_request: wp->length < 0")); 424 425 if ((p->flags & GV_PLEX_REBUILDING) && (boff + real_len < p->synced)) 426 type = REQ_TYPE_NORMAL; 427 428 if ((p->flags & GV_PLEX_REBUILDING) && (boff + real_len >= p->synced)) { 429 bioq_disksort(p->rqueue, bp); 430 *delay = 1; 431 return (0); 432 } 433 434 switch (bp->bio_cmd) { 435 case BIO_READ: 436 /* 437 * For a degraded read we need to read in all stripes except 438 * the broken one plus the parity stripe and then recalculate 439 * the desired data. 440 */ 441 if (type == REQ_TYPE_DEGRADED) { 442 bzero(wp->data, wp->length); 443 LIST_FOREACH(s, &p->subdisks, in_plex) { 444 /* Skip the broken subdisk. */ 445 if (s == broken) 446 continue; 447 /* Skip growing if within offset. */ 448 if (grow && s->flags & GV_SD_GROW) 449 continue; 450 cbp = gv_raid5_clone_bio(bp, s, wp, NULL, 1); 451 if (cbp == NULL) 452 return (ENOMEM); 453 454 bioq_insert_tail(p->bqueue, cbp); 455 456 bq = g_malloc(sizeof(*bq), M_WAITOK | M_ZERO); 457 bq->bp = cbp; 458 TAILQ_INSERT_TAIL(&wp->bits, bq, queue); 459 } 460 461 /* A normal read can be fulfilled with the original subdisk. */ 462 } else { 463 cbp = gv_raid5_clone_bio(bp, original, wp, addr, 0); 464 if (cbp == NULL) 465 return (ENOMEM); 466 467 bioq_insert_tail(p->bqueue, cbp); 468 } 469 wp->lockbase = -1; 470 471 break; 472 473 case BIO_WRITE: 474 /* 475 * A degraded write means we cannot write to the original data 476 * subdisk. Thus we need to read in all valid stripes, 477 * recalculate the parity from the original data, and then 478 * write the parity stripe back out. 479 */ 480 if (type == REQ_TYPE_DEGRADED) { 481 /* Read all subdisks. */ 482 LIST_FOREACH(s, &p->subdisks, in_plex) { 483 /* Skip the broken and the parity subdisk. */ 484 if ((s == broken) || (s == parity)) 485 continue; 486 /* Skip growing if within offset. */ 487 if (grow && s->flags & GV_SD_GROW) 488 continue; 489 490 cbp = gv_raid5_clone_bio(bp, s, wp, NULL, 1); 491 if (cbp == NULL) 492 return (ENOMEM); 493 cbp->bio_cmd = BIO_READ; 494 495 bioq_insert_tail(p->bqueue, cbp); 496 497 bq = g_malloc(sizeof(*bq), M_WAITOK | M_ZERO); 498 bq->bp = cbp; 499 TAILQ_INSERT_TAIL(&wp->bits, bq, queue); 500 } 501 502 /* Write the parity data. */ 503 cbp = gv_raid5_clone_bio(bp, parity, wp, NULL, 1); 504 if (cbp == NULL) 505 return (ENOMEM); 506 bcopy(addr, cbp->bio_data, wp->length); 507 wp->parity = cbp; 508 509 /* 510 * When the parity stripe is missing we just write out the data. 511 */ 512 } else if (type == REQ_TYPE_NOPARITY) { 513 cbp = gv_raid5_clone_bio(bp, original, wp, addr, 1); 514 if (cbp == NULL) 515 return (ENOMEM); 516 517 bioq_insert_tail(p->bqueue, cbp); 518 519 bq = g_malloc(sizeof(*bq), M_WAITOK | M_ZERO); 520 bq->bp = cbp; 521 TAILQ_INSERT_TAIL(&wp->bits, bq, queue); 522 523 /* 524 * A normal write request goes to the original subdisk, then we 525 * read in all other stripes, recalculate the parity and write 526 * out the parity again. 527 */ 528 } else { 529 /* Read old parity. */ 530 cbp = gv_raid5_clone_bio(bp, parity, wp, NULL, 1); 531 if (cbp == NULL) 532 return (ENOMEM); 533 cbp->bio_cmd = BIO_READ; 534 535 bioq_insert_tail(p->bqueue, cbp); 536 537 bq = g_malloc(sizeof(*bq), M_WAITOK | M_ZERO); 538 bq->bp = cbp; 539 TAILQ_INSERT_TAIL(&wp->bits, bq, queue); 540 541 /* Read old data. */ 542 cbp = gv_raid5_clone_bio(bp, original, wp, NULL, 1); 543 if (cbp == NULL) 544 return (ENOMEM); 545 cbp->bio_cmd = BIO_READ; 546 547 bioq_insert_tail(p->bqueue, cbp); 548 549 bq = g_malloc(sizeof(*bq), M_WAITOK | M_ZERO); 550 bq->bp = cbp; 551 TAILQ_INSERT_TAIL(&wp->bits, bq, queue); 552 553 /* Write new data. */ 554 cbp = gv_raid5_clone_bio(bp, original, wp, addr, 1); 555 if (cbp == NULL) 556 return (ENOMEM); 557 558 /* 559 * We must not write the new data until the old data 560 * was read, so hold this BIO back until we're ready 561 * for it. 562 */ 563 wp->waiting = cbp; 564 565 /* The final bio for the parity. */ 566 cbp = gv_raid5_clone_bio(bp, parity, wp, NULL, 1); 567 if (cbp == NULL) 568 return (ENOMEM); 569 570 /* Remember that this is the BIO for the parity data. */ 571 wp->parity = cbp; 572 } 573 break; 574 575 default: 576 return (EINVAL); 577 } 578 579 return (0); 580 } 581 582 /* 583 * Calculate the offsets in the various subdisks for a RAID5 request. Also take 584 * care of new subdisks in an expanded RAID5 array. 585 * XXX: This assumes that the new subdisks are inserted after the others (which 586 * is okay as long as plex_offset is larger). If subdisks are inserted into the 587 * plexlist before, we get problems. 588 */ 589 static int 590 gv_raid5_offset(struct gv_plex *p, off_t boff, off_t bcount, off_t *real_off, 591 off_t *real_len, int *sdno, int *psdno, int growing) 592 { 593 struct gv_sd *s; 594 int sd, psd, sdcount; 595 off_t len_left, stripeend, stripeoff, stripestart; 596 597 sdcount = p->sdcount; 598 if (growing) { 599 LIST_FOREACH(s, &p->subdisks, in_plex) { 600 if (s->flags & GV_SD_GROW) 601 sdcount--; 602 } 603 } 604 605 /* The number of the subdisk containing the parity stripe. */ 606 psd = sdcount - 1 - ( boff / (p->stripesize * (sdcount - 1))) % 607 sdcount; 608 KASSERT(psdno >= 0, ("gv_raid5_offset: psdno < 0")); 609 610 /* Offset of the start address from the start of the stripe. */ 611 stripeoff = boff % (p->stripesize * (sdcount - 1)); 612 KASSERT(stripeoff >= 0, ("gv_raid5_offset: stripeoff < 0")); 613 614 /* The number of the subdisk where the stripe resides. */ 615 sd = stripeoff / p->stripesize; 616 KASSERT(sdno >= 0, ("gv_raid5_offset: sdno < 0")); 617 618 /* At or past parity subdisk. */ 619 if (sd >= psd) 620 sd++; 621 622 /* The offset of the stripe on this subdisk. */ 623 stripestart = (boff - stripeoff) / (sdcount - 1); 624 KASSERT(stripestart >= 0, ("gv_raid5_offset: stripestart < 0")); 625 626 stripeoff %= p->stripesize; 627 628 /* The offset of the request on this subdisk. */ 629 *real_off = stripestart + stripeoff; 630 631 stripeend = stripestart + p->stripesize; 632 len_left = stripeend - *real_off; 633 KASSERT(len_left >= 0, ("gv_raid5_offset: len_left < 0")); 634 635 *real_len = (bcount <= len_left) ? bcount : len_left; 636 637 if (sdno != NULL) 638 *sdno = sd; 639 if (psdno != NULL) 640 *psdno = psd; 641 642 return (0); 643 } 644 645 static struct bio * 646 gv_raid5_clone_bio(struct bio *bp, struct gv_sd *s, struct gv_raid5_packet *wp, 647 caddr_t addr, int use_wp) 648 { 649 struct bio *cbp; 650 651 cbp = g_clone_bio(bp); 652 if (cbp == NULL) 653 return (NULL); 654 if (addr == NULL) { 655 cbp->bio_data = g_malloc(wp->length, M_WAITOK | M_ZERO); 656 cbp->bio_cflags |= GV_BIO_MALLOC; 657 } else 658 cbp->bio_data = addr; 659 cbp->bio_offset = wp->lockbase + s->drive_offset; 660 cbp->bio_length = wp->length; 661 cbp->bio_done = gv_done; 662 cbp->bio_caller1 = s; 663 s->drive_sc->active++; 664 if (use_wp) 665 cbp->bio_caller2 = wp; 666 667 return (cbp); 668 } 669