glxdri2.c revision 52397711
1/* 2 * Copyright © 2007 Red Hat, Inc 3 * 4 * Permission to use, copy, modify, distribute, and sell this software 5 * and its documentation for any purpose is hereby granted without 6 * fee, provided that the above copyright notice appear in all copies 7 * and that both that copyright notice and this permission notice 8 * appear in supporting documentation, and that the name of Red Hat, 9 * Inc not be used in advertising or publicity pertaining to 10 * distribution of the software without specific, written prior 11 * permission. Red Hat, Inc makes no representations about the 12 * suitability of this software for any purpose. It is provided "as 13 * is" without express or implied warranty. 14 * 15 * RED HAT, INC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN 17 * NO EVENT SHALL RED HAT, INC BE LIABLE FOR ANY SPECIAL, INDIRECT OR 18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 19 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 20 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 21 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 22 */ 23 24#ifdef HAVE_DIX_CONFIG_H 25#include <dix-config.h> 26#endif 27 28#include <stdint.h> 29#include <stdio.h> 30#include <string.h> 31#include <errno.h> 32#include <dlfcn.h> 33 34#include <drm.h> 35#include <GL/gl.h> 36#include <GL/internal/dri_interface.h> 37#include <GL/glxtokens.h> 38 39#include <windowstr.h> 40#include <os.h> 41 42#define _XF86DRI_SERVER_ 43#include <xf86drm.h> 44#include <xf86.h> 45#include <dri2.h> 46 47#include "glxserver.h" 48#include "glxutil.h" 49#include "glxdricommon.h" 50 51#include "g_disptab.h" 52#include "glapitable.h" 53#include "glapi.h" 54#include "glthread.h" 55#include "dispatch.h" 56#include "extension_string.h" 57 58typedef struct __GLXDRIscreen __GLXDRIscreen; 59typedef struct __GLXDRIcontext __GLXDRIcontext; 60typedef struct __GLXDRIdrawable __GLXDRIdrawable; 61 62struct __GLXDRIscreen { 63 __GLXscreen base; 64 __DRIscreen *driScreen; 65 void *driver; 66 int fd; 67 68 xf86EnterVTProc *enterVT; 69 xf86LeaveVTProc *leaveVT; 70 71 const __DRIcoreExtension *core; 72 const __DRIdri2Extension *dri2; 73 const __DRIcopySubBufferExtension *copySubBuffer; 74 const __DRIswapControlExtension *swapControl; 75 const __DRItexBufferExtension *texBuffer; 76 77 unsigned char glx_enable_bits[__GLX_EXT_BYTES]; 78}; 79 80struct __GLXDRIcontext { 81 __GLXcontext base; 82 __DRIcontext *driContext; 83}; 84 85#define MAX_DRAWABLE_BUFFERS 5 86 87struct __GLXDRIdrawable { 88 __GLXdrawable base; 89 __DRIdrawable *driDrawable; 90 __GLXDRIscreen *screen; 91 92 /* Dimensions as last reported by DRI2GetBuffers. */ 93 int width; 94 int height; 95 __DRIbuffer buffers[MAX_DRAWABLE_BUFFERS]; 96 int count; 97}; 98 99static void 100__glXDRIdrawableDestroy(__GLXdrawable *drawable) 101{ 102 __GLXDRIdrawable *private = (__GLXDRIdrawable *) drawable; 103 const __DRIcoreExtension *core = private->screen->core; 104 105 (*core->destroyDrawable)(private->driDrawable); 106 107 /* If the X window was destroyed, the dri DestroyWindow hook will 108 * aready have taken care of this, so only call if pDraw isn't NULL. */ 109 if (drawable->pDraw != NULL) 110 DRI2DestroyDrawable(drawable->pDraw); 111 112 __glXDrawableRelease(drawable); 113 114 xfree(private); 115} 116 117static void 118__glXDRIdrawableCopySubBuffer(__GLXdrawable *drawable, 119 int x, int y, int w, int h) 120{ 121 __GLXDRIdrawable *private = (__GLXDRIdrawable *) drawable; 122 BoxRec box; 123 RegionRec region; 124 125 box.x1 = x; 126 box.y1 = private->height - y - h; 127 box.x2 = x + w; 128 box.y2 = private->height - y; 129 REGION_INIT(drawable->pDraw->pScreen, ®ion, &box, 0); 130 131 DRI2CopyRegion(drawable->pDraw, ®ion, 132 DRI2BufferFrontLeft, DRI2BufferBackLeft); 133} 134 135static GLboolean 136__glXDRIdrawableSwapBuffers(__GLXdrawable *drawable) 137{ 138 __GLXDRIdrawable *private = (__GLXDRIdrawable *) drawable; 139 140 __glXDRIdrawableCopySubBuffer(drawable, 0, 0, 141 private->width, private->height); 142 143 return TRUE; 144} 145 146static void 147__glXDRIdrawableWaitX(__GLXdrawable *drawable) 148{ 149 __GLXDRIdrawable *private = (__GLXDRIdrawable *) drawable; 150 BoxRec box; 151 RegionRec region; 152 153 box.x1 = 0; 154 box.y1 = 0; 155 box.x2 = private->width; 156 box.y2 = private->height; 157 REGION_INIT(drawable->pDraw->pScreen, ®ion, &box, 0); 158 159 DRI2CopyRegion(drawable->pDraw, ®ion, 160 DRI2BufferFakeFrontLeft, DRI2BufferFrontLeft); 161} 162 163static void 164__glXDRIdrawableWaitGL(__GLXdrawable *drawable) 165{ 166 __GLXDRIdrawable *private = (__GLXDRIdrawable *) drawable; 167 BoxRec box; 168 RegionRec region; 169 170 box.x1 = 0; 171 box.y1 = 0; 172 box.x2 = private->width; 173 box.y2 = private->height; 174 REGION_INIT(drawable->pDraw->pScreen, ®ion, &box, 0); 175 176 DRI2CopyRegion(drawable->pDraw, ®ion, 177 DRI2BufferFrontLeft, DRI2BufferFakeFrontLeft); 178} 179 180static int 181__glXDRIdrawableSwapInterval(__GLXdrawable *drawable, int interval) 182{ 183 return 0; 184} 185 186static void 187__glXDRIcontextDestroy(__GLXcontext *baseContext) 188{ 189 __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext; 190 __GLXDRIscreen *screen = (__GLXDRIscreen *) context->base.pGlxScreen; 191 192 (*screen->core->destroyContext)(context->driContext); 193 __glXContextDestroy(&context->base); 194 xfree(context); 195} 196 197static int 198__glXDRIcontextMakeCurrent(__GLXcontext *baseContext) 199{ 200 __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext; 201 __GLXDRIdrawable *draw = (__GLXDRIdrawable *) baseContext->drawPriv; 202 __GLXDRIdrawable *read = (__GLXDRIdrawable *) baseContext->readPriv; 203 __GLXDRIscreen *screen = (__GLXDRIscreen *) context->base.pGlxScreen; 204 205 return (*screen->core->bindContext)(context->driContext, 206 draw->driDrawable, 207 read->driDrawable); 208} 209 210static int 211__glXDRIcontextLoseCurrent(__GLXcontext *baseContext) 212{ 213 __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext; 214 __GLXDRIscreen *screen = (__GLXDRIscreen *) context->base.pGlxScreen; 215 216 return (*screen->core->unbindContext)(context->driContext); 217} 218 219static int 220__glXDRIcontextCopy(__GLXcontext *baseDst, __GLXcontext *baseSrc, 221 unsigned long mask) 222{ 223 __GLXDRIcontext *dst = (__GLXDRIcontext *) baseDst; 224 __GLXDRIcontext *src = (__GLXDRIcontext *) baseSrc; 225 __GLXDRIscreen *screen = (__GLXDRIscreen *) dst->base.pGlxScreen; 226 227 return (*screen->core->copyContext)(dst->driContext, 228 src->driContext, mask); 229} 230 231static int 232__glXDRIcontextForceCurrent(__GLXcontext *baseContext) 233{ 234 __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext; 235 __GLXDRIdrawable *draw = (__GLXDRIdrawable *) baseContext->drawPriv; 236 __GLXDRIdrawable *read = (__GLXDRIdrawable *) baseContext->readPriv; 237 __GLXDRIscreen *screen = (__GLXDRIscreen *) context->base.pGlxScreen; 238 239 return (*screen->core->bindContext)(context->driContext, 240 draw->driDrawable, 241 read->driDrawable); 242} 243 244#ifdef __DRI_TEX_BUFFER 245 246static int 247__glXDRIbindTexImage(__GLXcontext *baseContext, 248 int buffer, 249 __GLXdrawable *glxPixmap) 250{ 251 __GLXDRIdrawable *drawable = (__GLXDRIdrawable *) glxPixmap; 252 const __DRItexBufferExtension *texBuffer = drawable->screen->texBuffer; 253 __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext; 254 255 if (texBuffer == NULL) 256 return Success; 257 258#if __DRI_TEX_BUFFER_VERSION >= 2 259 if (texBuffer->base.version >= 2 && texBuffer->setTexBuffer2 != NULL) { 260 (*texBuffer->setTexBuffer2)(context->driContext, 261 glxPixmap->target, 262 glxPixmap->format, 263 drawable->driDrawable); 264 } else 265#endif 266 { 267 texBuffer->setTexBuffer(context->driContext, 268 glxPixmap->target, 269 drawable->driDrawable); 270 } 271 272 return Success; 273} 274 275static int 276__glXDRIreleaseTexImage(__GLXcontext *baseContext, 277 int buffer, 278 __GLXdrawable *pixmap) 279{ 280 /* FIXME: Just unbind the texture? */ 281 return Success; 282} 283 284#else 285 286static int 287__glXDRIbindTexImage(__GLXcontext *baseContext, 288 int buffer, 289 __GLXdrawable *glxPixmap) 290{ 291 return Success; 292} 293 294static int 295__glXDRIreleaseTexImage(__GLXcontext *baseContext, 296 int buffer, 297 __GLXdrawable *pixmap) 298{ 299 return Success; 300} 301 302#endif 303 304static __GLXtextureFromPixmap __glXDRItextureFromPixmap = { 305 __glXDRIbindTexImage, 306 __glXDRIreleaseTexImage 307}; 308 309static void 310__glXDRIscreenDestroy(__GLXscreen *baseScreen) 311{ 312 __GLXDRIscreen *screen = (__GLXDRIscreen *) baseScreen; 313 314 (*screen->core->destroyScreen)(screen->driScreen); 315 316 dlclose(screen->driver); 317 318 __glXScreenDestroy(baseScreen); 319 320 xfree(screen); 321} 322 323static __GLXcontext * 324__glXDRIscreenCreateContext(__GLXscreen *baseScreen, 325 __GLXconfig *glxConfig, 326 __GLXcontext *baseShareContext) 327{ 328 __GLXDRIscreen *screen = (__GLXDRIscreen *) baseScreen; 329 __GLXDRIcontext *context, *shareContext; 330 __GLXDRIconfig *config = (__GLXDRIconfig *) glxConfig; 331 __DRIcontext *driShare; 332 333 shareContext = (__GLXDRIcontext *) baseShareContext; 334 if (shareContext) 335 driShare = shareContext->driContext; 336 else 337 driShare = NULL; 338 339 context = xcalloc(1, sizeof *context); 340 if (context == NULL) 341 return NULL; 342 343 context->base.destroy = __glXDRIcontextDestroy; 344 context->base.makeCurrent = __glXDRIcontextMakeCurrent; 345 context->base.loseCurrent = __glXDRIcontextLoseCurrent; 346 context->base.copy = __glXDRIcontextCopy; 347 context->base.forceCurrent = __glXDRIcontextForceCurrent; 348 context->base.textureFromPixmap = &__glXDRItextureFromPixmap; 349 350 context->driContext = 351 (*screen->dri2->createNewContext)(screen->driScreen, 352 config->driConfig, 353 driShare, context); 354 if (context->driContext == NULL) { 355 xfree(context); 356 return NULL; 357 } 358 359 return &context->base; 360} 361 362static __GLXdrawable * 363__glXDRIscreenCreateDrawable(__GLXscreen *screen, 364 DrawablePtr pDraw, 365 int type, 366 XID drawId, 367 __GLXconfig *glxConfig) 368{ 369 __GLXDRIscreen *driScreen = (__GLXDRIscreen *) screen; 370 __GLXDRIconfig *config = (__GLXDRIconfig *) glxConfig; 371 __GLXDRIdrawable *private; 372 373 private = xcalloc(1, sizeof *private); 374 if (private == NULL) 375 return NULL; 376 377 private->screen = driScreen; 378 if (!__glXDrawableInit(&private->base, screen, 379 pDraw, type, drawId, glxConfig)) { 380 xfree(private); 381 return NULL; 382 } 383 384 private->base.destroy = __glXDRIdrawableDestroy; 385 private->base.swapBuffers = __glXDRIdrawableSwapBuffers; 386 private->base.copySubBuffer = __glXDRIdrawableCopySubBuffer; 387 private->base.waitGL = __glXDRIdrawableWaitGL; 388 private->base.waitX = __glXDRIdrawableWaitX; 389 390 if (DRI2CreateDrawable(pDraw)) { 391 xfree(private); 392 return NULL; 393 } 394 395 private->driDrawable = 396 (*driScreen->dri2->createNewDrawable)(driScreen->driScreen, 397 config->driConfig, private); 398 399 return &private->base; 400} 401 402static __DRIbuffer * 403dri2GetBuffers(__DRIdrawable *driDrawable, 404 int *width, int *height, 405 unsigned int *attachments, int count, 406 int *out_count, void *loaderPrivate) 407{ 408 __GLXDRIdrawable *private = loaderPrivate; 409 DRI2BufferPtr *buffers; 410 int i; 411 int j; 412 413 buffers = DRI2GetBuffers(private->base.pDraw, 414 width, height, attachments, count, out_count); 415 if (*out_count > MAX_DRAWABLE_BUFFERS) { 416 *out_count = 0; 417 return NULL; 418 } 419 420 private->width = *width; 421 private->height = *height; 422 423 /* This assumes the DRI2 buffer attachment tokens matches the 424 * __DRIbuffer tokens. */ 425 j = 0; 426 for (i = 0; i < *out_count; i++) { 427 /* Do not send the real front buffer of a window to the client. 428 */ 429 if ((private->base.pDraw->type == DRAWABLE_WINDOW) 430 && (buffers[i]->attachment == DRI2BufferFrontLeft)) { 431 continue; 432 } 433 434 private->buffers[j].attachment = buffers[i]->attachment; 435 private->buffers[j].name = buffers[i]->name; 436 private->buffers[j].pitch = buffers[i]->pitch; 437 private->buffers[j].cpp = buffers[i]->cpp; 438 private->buffers[j].flags = buffers[i]->flags; 439 j++; 440 } 441 442 *out_count = j; 443 return private->buffers; 444} 445 446static __DRIbuffer * 447dri2GetBuffersWithFormat(__DRIdrawable *driDrawable, 448 int *width, int *height, 449 unsigned int *attachments, int count, 450 int *out_count, void *loaderPrivate) 451{ 452 __GLXDRIdrawable *private = loaderPrivate; 453 DRI2BufferPtr *buffers; 454 int i; 455 int j = 0; 456 457 buffers = DRI2GetBuffersWithFormat(private->base.pDraw, 458 width, height, attachments, count, 459 out_count); 460 if (*out_count > MAX_DRAWABLE_BUFFERS) { 461 *out_count = 0; 462 return NULL; 463 } 464 465 private->width = *width; 466 private->height = *height; 467 468 /* This assumes the DRI2 buffer attachment tokens matches the 469 * __DRIbuffer tokens. */ 470 for (i = 0; i < *out_count; i++) { 471 /* Do not send the real front buffer of a window to the client. 472 */ 473 if ((private->base.pDraw->type == DRAWABLE_WINDOW) 474 && (buffers[i]->attachment == DRI2BufferFrontLeft)) { 475 continue; 476 } 477 478 private->buffers[j].attachment = buffers[i]->attachment; 479 private->buffers[j].name = buffers[i]->name; 480 private->buffers[j].pitch = buffers[i]->pitch; 481 private->buffers[j].cpp = buffers[i]->cpp; 482 private->buffers[j].flags = buffers[i]->flags; 483 j++; 484 } 485 486 *out_count = j; 487 return private->buffers; 488} 489 490static void 491dri2FlushFrontBuffer(__DRIdrawable *driDrawable, void *loaderPrivate) 492{ 493 (void) driDrawable; 494 __glXDRIdrawableWaitGL((__GLXdrawable *) loaderPrivate); 495} 496 497static const __DRIdri2LoaderExtension loaderExtension = { 498 { __DRI_DRI2_LOADER, __DRI_DRI2_LOADER_VERSION }, 499 dri2GetBuffers, 500 dri2FlushFrontBuffer, 501 dri2GetBuffersWithFormat, 502}; 503 504static const __DRIextension *loader_extensions[] = { 505 &systemTimeExtension.base, 506 &loaderExtension.base, 507 NULL 508}; 509 510static const char dri_driver_path[] = DRI_DRIVER_PATH; 511 512static Bool 513glxDRIEnterVT (int index, int flags) 514{ 515 __GLXDRIscreen *screen = (__GLXDRIscreen *) 516 glxGetScreen(screenInfo.screens[index]); 517 518 LogMessage(X_INFO, "AIGLX: Resuming AIGLX clients after VT switch\n"); 519 520 if (!(*screen->enterVT) (index, flags)) 521 return FALSE; 522 523 glxResumeClients(); 524 525 return TRUE; 526} 527 528static void 529glxDRILeaveVT (int index, int flags) 530{ 531 __GLXDRIscreen *screen = (__GLXDRIscreen *) 532 glxGetScreen(screenInfo.screens[index]); 533 534 LogMessage(X_INFO, "AIGLX: Suspending AIGLX clients for VT switch\n"); 535 536 glxSuspendClients(); 537 538 return (*screen->leaveVT) (index, flags); 539} 540 541static void 542initializeExtensions(__GLXDRIscreen *screen) 543{ 544 const __DRIextension **extensions; 545 int i; 546 547 extensions = screen->core->getExtensions(screen->driScreen); 548 549 __glXEnableExtension(screen->glx_enable_bits, 550 "GLX_MESA_copy_sub_buffer"); 551 LogMessage(X_INFO, "AIGLX: enabled GLX_MESA_copy_sub_buffer\n"); 552 553 for (i = 0; extensions[i]; i++) { 554#ifdef __DRI_SWAP_CONTROL 555 if (strcmp(extensions[i]->name, __DRI_SWAP_CONTROL) == 0) { 556 screen->swapControl = 557 (const __DRIswapControlExtension *) extensions[i]; 558 __glXEnableExtension(screen->glx_enable_bits, 559 "GLX_SGI_swap_control"); 560 __glXEnableExtension(screen->glx_enable_bits, 561 "GLX_MESA_swap_control"); 562 563 LogMessage(X_INFO, "AIGLX: enabled GLX_SGI_swap_control and GLX_MESA_swap_control\n"); 564 } 565#endif 566 567#ifdef __DRI_TEX_BUFFER 568 if (strcmp(extensions[i]->name, __DRI_TEX_BUFFER) == 0) { 569 screen->texBuffer = 570 (const __DRItexBufferExtension *) extensions[i]; 571 /* GLX_EXT_texture_from_pixmap is always enabled. */ 572 LogMessage(X_INFO, "AIGLX: GLX_EXT_texture_from_pixmap backed by buffer objects\n"); 573 } 574#endif 575 /* Ignore unknown extensions */ 576 } 577} 578 579static __GLXscreen * 580__glXDRIscreenProbe(ScreenPtr pScreen) 581{ 582 const char *driverName, *deviceName; 583 __GLXDRIscreen *screen; 584 char filename[128]; 585 size_t buffer_size; 586 ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum]; 587 const __DRIextension **extensions; 588 const __DRIconfig **driConfigs; 589 int i; 590 591 screen = xcalloc(1, sizeof *screen); 592 if (screen == NULL) 593 return NULL; 594 595 if (!xf86LoaderCheckSymbol("DRI2Connect") || 596 !DRI2Connect(pScreen, DRI2DriverDRI, 597 &screen->fd, &driverName, &deviceName)) { 598 LogMessage(X_INFO, 599 "AIGLX: Screen %d is not DRI2 capable\n", pScreen->myNum); 600 return NULL; 601 } 602 603 screen->base.destroy = __glXDRIscreenDestroy; 604 screen->base.createContext = __glXDRIscreenCreateContext; 605 screen->base.createDrawable = __glXDRIscreenCreateDrawable; 606 screen->base.swapInterval = __glXDRIdrawableSwapInterval; 607 screen->base.pScreen = pScreen; 608 609 __glXInitExtensionEnableBits(screen->glx_enable_bits); 610 611 snprintf(filename, sizeof filename, 612 "%s/%s_dri.so", dri_driver_path, driverName); 613 614 screen->driver = dlopen(filename, RTLD_LAZY | RTLD_LOCAL); 615 if (screen->driver == NULL) { 616 LogMessage(X_ERROR, "AIGLX error: dlopen of %s failed (%s)\n", 617 filename, dlerror()); 618 goto handle_error; 619 } 620 621 extensions = dlsym(screen->driver, __DRI_DRIVER_EXTENSIONS); 622 if (extensions == NULL) { 623 LogMessage(X_ERROR, "AIGLX error: %s exports no extensions (%s)\n", 624 driverName, dlerror()); 625 goto handle_error; 626 } 627 628 for (i = 0; extensions[i]; i++) { 629 if (strcmp(extensions[i]->name, __DRI_CORE) == 0 && 630 extensions[i]->version >= __DRI_CORE_VERSION) { 631 screen->core = (const __DRIcoreExtension *) extensions[i]; 632 } 633 if (strcmp(extensions[i]->name, __DRI_DRI2) == 0 && 634 extensions[i]->version >= __DRI_DRI2_VERSION) { 635 screen->dri2 = (const __DRIdri2Extension *) extensions[i]; 636 } 637 } 638 639 if (screen->core == NULL || screen->dri2 == NULL) { 640 LogMessage(X_ERROR, "AIGLX error: %s exports no DRI extension\n", 641 driverName); 642 goto handle_error; 643 } 644 645 screen->driScreen = 646 (*screen->dri2->createNewScreen)(pScreen->myNum, 647 screen->fd, 648 loader_extensions, 649 &driConfigs, 650 screen); 651 652 if (screen->driScreen == NULL) { 653 LogMessage(X_ERROR, 654 "AIGLX error: Calling driver entry point failed\n"); 655 goto handle_error; 656 } 657 658 initializeExtensions(screen); 659 660 screen->base.fbconfigs = glxConvertConfigs(screen->core, driConfigs); 661 662 __glXScreenInit(&screen->base, pScreen); 663 664 buffer_size = __glXGetExtensionString(screen->glx_enable_bits, NULL); 665 if (buffer_size > 0) { 666 if (screen->base.GLXextensions != NULL) { 667 xfree(screen->base.GLXextensions); 668 } 669 670 screen->base.GLXextensions = xnfalloc(buffer_size); 671 (void) __glXGetExtensionString(screen->glx_enable_bits, 672 screen->base.GLXextensions); 673 } 674 675 screen->enterVT = pScrn->EnterVT; 676 pScrn->EnterVT = glxDRIEnterVT; 677 screen->leaveVT = pScrn->LeaveVT; 678 pScrn->LeaveVT = glxDRILeaveVT; 679 680 LogMessage(X_INFO, 681 "AIGLX: Loaded and initialized %s\n", filename); 682 683 return &screen->base; 684 685 handle_error: 686 if (screen->driver) 687 dlclose(screen->driver); 688 689 xfree(screen); 690 691 LogMessage(X_ERROR, "AIGLX: reverting to software rendering\n"); 692 693 return NULL; 694} 695 696__GLXprovider __glXDRI2Provider = { 697 __glXDRIscreenProbe, 698 "DRI2", 699 NULL 700}; 701