|
|
1.1 ! root 1: /* $Header: /x/usr/src/sys/coh/RCS/bio.c,v 1.1 91/11/08 12:46:39 hal Exp $ */ ! 2: /* (lgl- ! 3: * The information contained herein is a trade secret of Mark Williams ! 4: * Company, and is confidential information. It is provided under a ! 5: * license agreement, and may be copied or disclosed only under the ! 6: * terms of that agreement. Any reproduction or disclosure of this ! 7: * material without the express written authorization of Mark Williams ! 8: * Company or persuant to the license agreement is unlawful. ! 9: * ! 10: * COHERENT Version 2.3.37 ! 11: * Copyright (c) 1982, 1983, 1984. ! 12: * An unpublished work by Mark Williams Company, Chicago. ! 13: * All rights reserved. ! 14: -lgl) */ ! 15: /* ! 16: * Coherent. ! 17: * Buffered I/O. ! 18: * ! 19: * $Log: bio.c,v $ ! 20: * Revision 1.1 91/11/08 12:46:39 hal ! 21: * Used in COH 3.2.0 ! 22: * ! 23: * Revision 1.1 88/03/24 16:13:29 src ! 24: * Initial revision ! 25: * ! 26: * 87/11/25 Allan Cornish /usr/src/sys/coh/bio.c ! 27: * vaddr_t bp->b_vaddr --> faddr_t bp->b_faddr. ! 28: * ! 29: * 87/11/05 Allan Cornish /usr/src/sys/coh/bio.c ! 30: * New seg struct now used to allow extended addressing. ! 31: * ! 32: * 87/01/05 Allan Cornish /usr/src/sys/coh/bio.c ! 33: * ioreq() now only wakes &stimer if the swap timer is active. ! 34: * ! 35: * 86/12/12 Allan Cornish /usr/src/sys/coh/bio.c ! 36: * Added 3rd arg to dpoll() to specify blocking poll if non-zero. ! 37: * ! 38: * 86/11/19 Allan Cornish /usr/src/sys/coh/bio.c ! 39: * Added dpoll() routine to perform device polls [System V.3 compatible]. ! 40: * ! 41: * 86/07/24 Allan Cornish /usr/src/sys/coh/bio.c ! 42: * Added check in devinit() for null dp->d_conp->c_load function pointer. ! 43: */ ! 44: #include <sys/coherent.h> ! 45: #include <sys/buf.h> ! 46: #include <sys/con.h> ! 47: #include <errno.h> ! 48: #include <sys/io.h> ! 49: #include <sys/proc.h> ! 50: #include <sys/sched.h> ! 51: #include <sys/seg.h> ! 52: #include <sys/stat.h> ! 53: #include <sys/uproc.h> ! 54: ! 55: /* ! 56: * Initialise buffer headers. ! 57: */ ! 58: bufinit() ! 59: { ! 60: register BUF *bp; ! 61: faddr_t f; ! 62: paddr_t p; ! 63: ! 64: FP_SEL(f) = sds; ! 65: FP_OFF(f) = 0; ! 66: p = blockp; ! 67: FP_OFF(f) = blockp - vtop(f); ! 68: ! 69: bufl = kalloc(NBUF * sizeof(BUF)); ! 70: if (bufl == NULL) ! 71: panic("bufinit: no space for BUF's"); ! 72: ! 73: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 74: bp->b_dev = NODEV; ! 75: bp->b_faddr = f; ! 76: bp->b_paddr = p; ! 77: FP_OFF(f) += BSIZE; ! 78: p += BSIZE; ! 79: } ! 80: } ! 81: ! 82: /* ! 83: * Synchronise the buffer cache. ! 84: */ ! 85: bsync() ! 86: { ! 87: register BUF *bp; ! 88: ! 89: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 90: if ((bp->b_flag&BFMOD) == 0) ! 91: continue; ! 92: lock(bp->b_gate); ! 93: if ((bp->b_flag&BFMOD) != 0) ! 94: bwrite(bp, 1); ! 95: unlock(bp->b_gate); ! 96: } ! 97: } ! 98: ! 99: /* ! 100: * Synchronise all block for a particular device in the buffer cache ! 101: * and invalidate all references. ! 102: */ ! 103: bflush(dev) ! 104: register dev_t dev; ! 105: { ! 106: register BUF *bp; ! 107: ! 108: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 109: if (bp->b_dev != dev) ! 110: continue; ! 111: lock(bp->b_gate); ! 112: if (bp->b_dev == dev) { ! 113: if ((bp->b_flag&BFMOD) != 0) ! 114: bwrite(bp, 1); ! 115: bp->b_dev = NODEV; ! 116: } ! 117: unlock(bp->b_gate); ! 118: } ! 119: } ! 120: ! 121: /* ! 122: * Return a buffer containing the given block from the given device. ! 123: * If `f' is not set, the read is asynchronous and no buffer is returned. ! 124: */ ! 125: BUF * ! 126: bread(dev, bno, f) ! 127: dev_t dev; ! 128: daddr_t bno; ! 129: register int f; ! 130: { ! 131: register BUF *bp; ! 132: register int s; ! 133: ! 134: bp = bclaim(dev, bno); ! 135: if ((bp->b_flag&BFNTP) != 0) { ! 136: if (f != 0) ! 137: bp->b_flag &= ~BFASY; ! 138: else { ! 139: bp->b_flag |= BFASY; ! 140: bumap(bp); ! 141: } ! 142: bp->b_req = BREAD; ! 143: bp->b_count = BSIZE; ! 144: s = sphi(); ! 145: dblock(dev, bp); ! 146: if (f == 0) { ! 147: spl(s); ! 148: return (NULL); ! 149: } ! 150: while ((bp->b_flag&BFNTP) != 0) ! 151: sleep((char *)bp, CVBLKIO, IVBLKIO, SVBLKIO); ! 152: spl(s); ! 153: if ((bp->b_flag&BFERR) != 0) { ! 154: u.u_error = bp->b_err ? bp->b_err : EIO; ! 155: brelease(bp); ! 156: return (NULL); ! 157: } ! 158: if (bp->b_resid == BSIZE) { ! 159: brelease(bp); ! 160: return (NULL); ! 161: } ! 162: } ! 163: if (f == 0) { ! 164: brelease(bp); ! 165: return (NULL); ! 166: } ! 167: u.u_block++; ! 168: return (bp); ! 169: } ! 170: ! 171: /* ! 172: * If the requested buffer is in the buffer cache, return a pointer to ! 173: * it. If not, pick an empty buffer, set it up and return it. ! 174: */ ! 175: BUF * ! 176: bclaim(dev, bno) ! 177: dev_t dev; ! 178: daddr_t bno; ! 179: { ! 180: register BUF *bp; ! 181: register BUF *bp1; ! 182: register unsigned seqn; ! 183: register int s; ! 184: ! 185: again: ! 186: bp1 = NULL; ! 187: seqn = 0; ! 188: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 189: if (bp->b_bno == bno && bp->b_dev == dev) { ! 190: lock(bp->b_gate); ! 191: if (bp->b_bno != bno || bp->b_dev != dev) { ! 192: unlock(bp->b_gate); ! 193: goto again; ! 194: } ! 195: if ((bp->b_flag&BFERR) != 0) ! 196: bp->b_flag |= BFNTP; ! 197: bsmap(bp); ! 198: return (bp); ! 199: } ! 200: if (locked(bp->b_gate) == 0) { ! 201: if (bufseqn-bp->b_seqn >= seqn) { ! 202: bp1 = bp; ! 203: seqn = bufseqn - bp->b_seqn; ! 204: } ! 205: } ! 206: } ! 207: if (bp1 == NULL) { ! 208: s = sphi(); ! 209: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 210: if (locked(bp->b_gate) == 0) { ! 211: if (bufseqn-bp->b_seqn >= seqn) { ! 212: bp1 = bp; ! 213: seqn = bufseqn - bp->b_seqn; ! 214: } ! 215: } ! 216: } ! 217: if (bp1 == NULL) { ! 218: bufneed = 1; ! 219: sleep((char *)&bufneed, CVBLKIO, IVBLKIO, SVBLKIO); ! 220: spl(s); ! 221: goto again; ! 222: } ! 223: spl(s); ! 224: } ! 225: bp = bp1; ! 226: lock(bp->b_gate); ! 227: if ((bp->b_flag&BFMOD) != 0) { ! 228: bwrite(bp, 0); ! 229: goto again; ! 230: } ! 231: bp->b_flag = BFNTP; ! 232: bp->b_dev = dev; ! 233: bp->b_bno = bno; ! 234: bsmap(bp); ! 235: return (bp); ! 236: } ! 237: ! 238: /* ! 239: * Write the given buffer out. If `f' is set, the write is synchronous, ! 240: * otherwise asynchronous. This routine must be called with the buffer ! 241: * gate locked. ! 242: */ ! 243: bwrite(bp, f) ! 244: register BUF *bp; ! 245: { ! 246: register int s; ! 247: ! 248: if (f != 0) ! 249: bp->b_flag &= ~BFASY; ! 250: else { ! 251: bp->b_flag |= BFASY; ! 252: bumap(bp); ! 253: } ! 254: bp->b_flag |= BFNTP; ! 255: bp->b_req = BWRITE; ! 256: bp->b_count = BSIZE; ! 257: s = sphi(); ! 258: dblock(bp->b_dev, bp); ! 259: if (f == 0) { ! 260: spl(s); ! 261: return; ! 262: } ! 263: while ((bp->b_flag&BFNTP) != 0) ! 264: sleep((char *)bp, CVBLKIO, IVBLKIO, SVBLKIO); ! 265: spl(s); ! 266: } ! 267: ! 268: /* ! 269: * This is called by the driver when I/O has completed on a buffer. ! 270: */ ! 271: bdone(bp) ! 272: register BUF *bp; ! 273: { ! 274: if (bp->b_req == BWRITE) ! 275: bp->b_flag &= ~BFMOD; ! 276: if (bp->b_req == BREAD) { ! 277: if ((bp->b_flag&BFERR) != 0) ! 278: bp->b_dev = NODEV; ! 279: } ! 280: if ((bp->b_flag&BFASY) != 0) { ! 281: bp->b_flag &= ~BFASY; ! 282: brelease(bp); ! 283: } ! 284: bp->b_flag &= ~BFNTP; ! 285: wakeup((char *)bp); ! 286: } ! 287: ! 288: /* ! 289: * Release the given buffer. ! 290: */ ! 291: brelease(bp) ! 292: register BUF *bp; ! 293: { ! 294: if ((bp->b_flag&BFERR) == 0) ! 295: bp->b_seqn = bufseqn++; ! 296: else { ! 297: bp->b_flag &= ~BFERR; ! 298: bp->b_dev = NODEV; ! 299: } ! 300: bp->b_flag &= ~BFNTP; ! 301: bumap(bp); ! 302: unlock(bp->b_gate); ! 303: if (bufneed != 0) { ! 304: bufneed = 0; ! 305: wakeup((char *)&bufneed); ! 306: } ! 307: } ! 308: ! 309: /* ! 310: * Map the given buffer. ! 311: */ ! 312: bsmap(bp) ! 313: register BUF *bp; ! 314: { ! 315: bsave(bp->b_map); ! 316: bp->b_flag |= BFMAP; ! 317: bmapv(bconv(bp->b_paddr)); ! 318: } ! 319: ! 320: /* ! 321: * Unmap the given buffer. ! 322: */ ! 323: bumap(bp) ! 324: register BUF *bp; ! 325: { ! 326: if ((bp->b_flag&BFMAP) == 0) ! 327: return; ! 328: bp->b_flag &= ~BFMAP; ! 329: brest(bp->b_map); ! 330: } ! 331: ! 332: /* ! 333: * Read data from the I/O segment into kernel space. ! 334: */ ! 335: ioread(iop, v, n) ! 336: register IO *iop; ! 337: register char *v; ! 338: register unsigned n; ! 339: { ! 340: switch (iop->io_seg) { ! 341: case IOSYS: ! 342: iop->io_base += kkcopy(iop->io_base, v, n); ! 343: break; ! 344: case IOUSR: ! 345: iop->io_base += ukcopy(iop->io_base, v, n); ! 346: break; ! 347: case IOPHY: ! 348: iop->io_phys += pkcopy(iop->io_phys, v, n); ! 349: break; ! 350: } ! 351: iop->io_ioc -= n; ! 352: } ! 353: ! 354: /* ! 355: * Write data from kernel space to the I/O segment. ! 356: */ ! 357: iowrite(iop, v, n) ! 358: register IO *iop; ! 359: register char *v; ! 360: register unsigned n; ! 361: { ! 362: switch (iop->io_seg) { ! 363: case IOSYS: ! 364: iop->io_base += kkcopy(v, iop->io_base, n); ! 365: break; ! 366: case IOUSR: ! 367: iop->io_base += kucopy(v, iop->io_base, n); ! 368: break; ! 369: case IOPHY: ! 370: iop->io_phys += kpcopy(v, iop->io_phys, n); ! 371: break; ! 372: } ! 373: iop->io_ioc -= n; ! 374: } ! 375: ! 376: /* ! 377: * Get a character from the I/O segment. ! 378: */ ! 379: iogetc(iop) ! 380: register IO *iop; ! 381: { ! 382: register int c; ! 383: ! 384: if (iop->io_ioc == 0) ! 385: return (-1); ! 386: --iop->io_ioc; ! 387: if (iop->io_seg == IOSYS) ! 388: c = *iop->io_base++ & 0377; ! 389: else { ! 390: c = getubd(iop->io_base++); ! 391: if (u.u_error) ! 392: return (-1); ! 393: } ! 394: return (c); ! 395: } ! 396: ! 397: /* ! 398: * Put a character using the I/O segment. ! 399: */ ! 400: ioputc(c, iop) ! 401: register IO *iop; ! 402: { ! 403: if (iop->io_ioc == 0) ! 404: return (-1); ! 405: --iop->io_ioc; ! 406: if (iop->io_seg == IOSYS) ! 407: *iop->io_base++ = c; ! 408: else { ! 409: putubd(iop->io_base++, c); ! 410: if (u.u_error) ! 411: return (-1); ! 412: } ! 413: return (c); ! 414: } ! 415: ! 416: /* ! 417: * Given a buffer pointer, an I/O structure, a device, request type, and ! 418: * a flags word, check the I/O structure and perform the I/O request. ! 419: */ ! 420: ioreq(bp, iop, dev, req, f) ! 421: register BUF *bp; ! 422: register IO *iop; ! 423: dev_t dev; ! 424: { ! 425: register SEG *sp; ! 426: register int n; ! 427: register int s; ! 428: register CON *cp; ! 429: dold_t dold; ! 430: ! 431: if ((cp=drvmap(dev, &dold)) == NULL) ! 432: return; ! 433: lock(bp->b_gate); ! 434: n = cp->c_flag; /* n should do something with that flag */ ! 435: drest(dold); ! 436: sp = NULL; ! 437: if (iop != NULL) { ! 438: if ((f&BFBLK) != 0) { ! 439: if (blocko(iop->io_seek) != 0) { ! 440: u.u_error = EIO; ! 441: goto out; ! 442: } ! 443: } ! 444: if ((f&BFIOC) != 0) { ! 445: if ((sp=iomapvp(iop, bp)) == NULL) { ! 446: u.u_error = EIO; ! 447: goto out; ! 448: } ! 449: } ! 450: } ! 451: bp->b_flag = f|BFNTP; ! 452: bp->b_req = req; ! 453: bp->b_dev = dev; ! 454: if (iop != NULL) { ! 455: bp->b_bno = blockn(iop->io_seek); ! 456: bp->b_count = iop->io_ioc; ! 457: } ! 458: if (sp != NULL) { ! 459: bp->b_faddr = ptov( bp->b_paddr, (fsize_t) bp->b_count ); ! 460: sp->s_lrefc++; ! 461: } ! 462: s = sphi(); ! 463: dblock(dev, bp); ! 464: while ((bp->b_flag&BFNTP) != 0) ! 465: sleep((char *)bp, CVBLKIO, IVBLKIO, SVBLKIO); ! 466: spl(s); ! 467: if (sp != NULL) { ! 468: vrelse( bp->b_faddr ); ! 469: sp->s_lrefc--; ! 470: } ! 471: if (stimer.t_last != 0) ! 472: wakeup((char *)&stimer); ! 473: if ((bp->b_flag&BFERR) != 0) { ! 474: u.u_error = bp->b_err ? bp->b_err : EIO; ! 475: goto out; ! 476: } ! 477: if (iop != NULL) { ! 478: n = iop->io_ioc - bp->b_resid; ! 479: iop->io_seek += n; ! 480: iop->io_ioc -= n; ! 481: } ! 482: out: ! 483: unlock(bp->b_gate); ! 484: } ! 485: ! 486: /* ! 487: * Given an I/O structure and a buffer header, see if the addresses ! 488: * in the I/O structure are valid and set up the buffer header. ! 489: */ ! 490: SEG * ! 491: iomapvp(iop, bp) ! 492: register IO *iop; ! 493: register BUF *bp; ! 494: { ! 495: register SR *srp; ! 496: register SEG *sp; ! 497: register vaddr_t b; ! 498: ! 499: if (iop->io_seg != IOUSR) ! 500: panic("Raw I/O from non user"); ! 501: for (srp=u.u_segl; srp<&u.u_segl[NUSEG]; srp++) { ! 502: if ((sp=srp->sr_segp) == NULL) ! 503: continue; ! 504: if ((srp->sr_flag&SRFDATA) == 0) ! 505: continue; ! 506: /* Yet another bug in the 8000 C compiler ! 507: if ((long)(b=iop->io_base) < (long)srp->sr_base) ! 508: */ ! 509: if ((b=iop->io_base) < srp->sr_base) ! 510: continue; ! 511: if ((long)b+iop->io_ioc > (long)srp->sr_base + sp->s_size) ! 512: continue; ! 513: bp->b_paddr = sp->s_paddr + (vaddr_t) (b - srp->sr_base); ! 514: return (sp); ! 515: } ! 516: return (NULL); ! 517: } ! 518: ! 519: /* ! 520: * Initialise devices. ! 521: */ ! 522: devinit() ! 523: { ! 524: register DRV *dp; ! 525: register int mind; ! 526: ! 527: for ( dp = drvl, mind = 0; mind < drvn; mind++, dp++ ) { ! 528: if ((dp->d_conp != NULL) && (dp->d_conp->c_load != NULL)) { ! 529: (*dp->d_conp->c_load)(); ! 530: } ! 531: } ! 532: } ! 533: ! 534: /* ! 535: * Open a device. ! 536: */ ! 537: dopen(dev, m, f) ! 538: register dev_t dev; ! 539: { ! 540: register CON *cp; ! 541: dold_t dold; ! 542: ! 543: if ((cp=drvmap(dev, &dold)) == NULL) ! 544: return; ! 545: if ((cp->c_flag&f) == 0) { ! 546: u.u_error = ENXIO; ! 547: return; ! 548: } ! 549: (*cp->c_open)(dev, m); ! 550: drest(dold); ! 551: } ! 552: ! 553: /* ! 554: * Close a device. ! 555: */ ! 556: dclose(dev) ! 557: register dev_t dev; ! 558: { ! 559: register CON *cp; ! 560: dold_t dold; ! 561: ! 562: if ((cp=drvmap(dev, &dold)) == NULL) ! 563: return; ! 564: (*cp->c_close)(dev); ! 565: drest(dold); ! 566: } ! 567: ! 568: /* ! 569: * Call the block entry point of a device. ! 570: */ ! 571: dblock(dev, bp) ! 572: dev_t dev; ! 573: BUF *bp; ! 574: { ! 575: register CON *cp; ! 576: dold_t dold; ! 577: ! 578: if ((cp=drvmap(dev, &dold)) == NULL) ! 579: return; ! 580: (*cp->c_block)(bp); ! 581: drest(dold); ! 582: } ! 583: ! 584: /* ! 585: * Read from a device. ! 586: */ ! 587: dread(dev, iop) ! 588: register dev_t dev; ! 589: register IO *iop; ! 590: { ! 591: register CON *cp; ! 592: dold_t dold; ! 593: ! 594: if ((cp=drvmap(dev, &dold)) == NULL) ! 595: return; ! 596: (*cp->c_read)(dev, iop); ! 597: drest(dold); ! 598: } ! 599: ! 600: /* ! 601: * Write to a device. ! 602: */ ! 603: dwrite(dev, iop) ! 604: register dev_t dev; ! 605: register IO *iop; ! 606: { ! 607: register CON *cp; ! 608: dold_t dold; ! 609: ! 610: if ((cp=drvmap(dev, &dold)) == NULL) ! 611: return; ! 612: (*cp->c_write)(dev, iop); ! 613: drest(dold); ! 614: } ! 615: ! 616: /* ! 617: * Call the ioctl function for a device. ! 618: */ ! 619: dioctl(dev, com, vec) ! 620: register dev_t dev; ! 621: union ioctl *vec; ! 622: { ! 623: register CON *cp; ! 624: dold_t dold; ! 625: ! 626: if ((cp=drvmap(dev, &dold)) == NULL) ! 627: return; ! 628: (*cp->c_ioctl)(dev, com, vec); ! 629: drest(dold); ! 630: } ! 631: ! 632: /* ! 633: * Call the powerfail entry point of a device. ! 634: */ ! 635: dpower(dev) ! 636: register dev_t dev; ! 637: { ! 638: register CON *cp; ! 639: dold_t dold; ! 640: ! 641: if ((cp=drvmap(dev, &dold)) == NULL) ! 642: return; ! 643: (*cp->c_power)(dev); ! 644: drest(dold); ! 645: } ! 646: ! 647: /* ! 648: * Call the timeout entry point of a device. ! 649: */ ! 650: dtime(dev) ! 651: register dev_t dev; ! 652: { ! 653: register CON *cp; ! 654: dold_t dold; ! 655: ! 656: if ((cp=drvmap(dev, &dold)) == NULL) ! 657: return; ! 658: (*cp->c_timer)(dev); ! 659: drest(dold); ! 660: } ! 661: ! 662: /* ! 663: * Poll a device. ! 664: */ ! 665: dpoll(dev, ev, msec) ! 666: register dev_t dev; ! 667: int ev; ! 668: int msec; ! 669: { ! 670: register CON *cp; ! 671: dold_t dold; ! 672: ! 673: if ((cp=drvmap(dev, &dold)) == NULL) ! 674: return POLLNVAL; ! 675: ! 676: if ( cp->c_flag & DFPOL ) ! 677: ev = (*cp->c_poll)(dev, ev, msec); ! 678: else ! 679: ev = POLLNVAL; ! 680: ! 681: drest(dold); ! 682: return ev; ! 683: } ! 684: ! 685: /* ! 686: * Given a device, return the flags word. ! 687: */ ! 688: dflag(dev) ! 689: dev_t dev; ! 690: { ! 691: register CON *cp; ! 692: register int f; ! 693: dold_t dold; ! 694: ! 695: if ((cp=drvmap(dev, &dold)) == NULL) ! 696: return (DFERR); ! 697: f = cp->c_flag; ! 698: drest(dold); ! 699: return (f); ! 700: } ! 701: ! 702: /* ! 703: * Given a device, and a pointer to a driver map save area, save the ! 704: * current map in the driver map save area and map in the new device, ! 705: * returning a pointer to the configuration entry for that device. ! 706: */ ! 707: CON * ! 708: drvmap(dev, doldp) ! 709: dev_t dev; ! 710: dold_t *doldp; ! 711: { ! 712: register DRV *dp; ! 713: register unsigned m; ! 714: ! 715: if ((m=major(dev)) >= drvn) { ! 716: u.u_error = ENXIO; ! 717: return (NULL); ! 718: } ! 719: dp = &drvl[m]; ! 720: if (locked(dp->d_gate)) { ! 721: u.u_error = ENXIO; ! 722: return (NULL); ! 723: } ! 724: if (dp->d_conp == NULL) { ! 725: u.u_error = ENXIO; ! 726: return (NULL); ! 727: } ! 728: dsave(*doldp); ! 729: if (dp->d_map != 0) ! 730: dmapv(dp->d_map); ! 731: return (dp->d_conp); ! 732: } ! 733: ! 734: /* ! 735: * Non existant device. ! 736: */ ! 737: nonedev() ! 738: { ! 739: u.u_error = ENXIO; ! 740: } ! 741: ! 742: /* ! 743: * Null device. ! 744: */ ! 745: nulldev() ! 746: { ! 747: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.