diff options
Diffstat (limited to 'saa/saa.c')
-rw-r--r-- | saa/saa.c | 748 |
1 files changed, 748 insertions, 0 deletions
diff --git a/saa/saa.c b/saa/saa.c new file mode 100644 index 0000000..173c090 --- /dev/null +++ b/saa/saa.c @@ -0,0 +1,748 @@ +/* + * Copyright © 2001 Keith Packard + * + * Partly based on code that is Copyright © The XFree86 Project Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/** @file + * This file covers the initialization and teardown of SAA, and has various + * functions not responsible for performing rendering, pixmap migration, or + * memory management. + */ + +#ifdef HAVE_DIX_CONFIG_H +#include <dix-config.h> +#endif + +#include <stdlib.h> + +#include "saa_priv.h" +#include <X11/fonts/fontstruct.h> +#include "regionstr.h" +#include "saa.h" +#include "saa_priv.h" + +#ifdef SAA_DEVPRIVATEKEYREC +DevPrivateKeyRec saa_screen_index; +DevPrivateKeyRec saa_pixmap_index; +DevPrivateKeyRec saa_gc_index; +#else +int saa_screen_index = -1; +int saa_pixmap_index = -1; +int saa_gc_index = -1; +#endif + +/** + * saa_get_drawable_pixmap() returns a backing pixmap for a given drawable. + * + * @param pDrawable the drawable being requested. + * + * This function returns the backing pixmap for a drawable, whether it is a + * redirected window, unredirected window, or already a pixmap. Note that + * coordinate translation is needed when drawing to the backing pixmap of a + * redirected window, and the translation coordinates are provided by calling + * saa_get_drawable_pixmap() on the drawable. + */ +PixmapPtr +saa_get_drawable_pixmap(DrawablePtr pDrawable) +{ + if (pDrawable->type == DRAWABLE_WINDOW) + return pDrawable->pScreen->GetWindowPixmap((WindowPtr) pDrawable); + else + return (PixmapPtr) pDrawable; +} + +/** + * Sets the offsets to add to coordinates to make them address the same bits in + * the backing drawable. These coordinates are nonzero only for redirected + * windows. + */ +void +saa_get_drawable_deltas(DrawablePtr pDrawable, PixmapPtr pPixmap, + int *xp, int *yp) +{ +#ifdef COMPOSITE + if (pDrawable->type == DRAWABLE_WINDOW) { + *xp = -pPixmap->screen_x; + *yp = -pPixmap->screen_y; + return; + } +#endif + + *xp = 0; + *yp = 0; +} + +/** + * Returns the pixmap which backs a drawable, and the offsets to add to + * coordinates to make them address the same bits in the backing drawable. + */ +PixmapPtr +saa_get_pixmap(DrawablePtr drawable, int *xp, int *yp) +{ + PixmapPtr pixmap = saa_get_drawable_pixmap(drawable); + + saa_get_drawable_deltas(drawable, pixmap, xp, yp); + + return pixmap; +} + +static Bool +saa_download_from_hw(PixmapPtr pix, RegionPtr readback) +{ + struct saa_screen_priv *sscreen = saa_screen(pix->drawable.pScreen); + struct saa_driver *driver = sscreen->driver; + struct saa_pixmap *spix = saa_pixmap(pix); + void *addr; + Bool ret; + + if (spix->mapped_access) + driver->release_from_cpu(driver, pix, spix->mapped_access); + + ret = driver->download_from_hw(driver, pix, readback); + + if (spix->mapped_access) { + addr = driver->sync_for_cpu(driver, pix, spix->mapped_access); + if (addr != NULL) + spix->addr = addr; + } + + return ret; +} + +Bool +saa_prepare_access_pixmap(PixmapPtr pix, saa_access_t access, + RegionPtr read_reg) +{ + ScreenPtr pScreen = pix->drawable.pScreen; + struct saa_screen_priv *sscreen = saa_screen(pScreen); + struct saa_driver *driver = sscreen->driver; + struct saa_pixmap *spix = saa_pixmap(pix); + saa_access_t map_access = 0; + Bool ret = TRUE; + + if (read_reg && REGION_NOTEMPTY(pScreen, read_reg)) + ret = saa_download_from_hw(pix, read_reg); + + if (!ret) { + LogMessage(X_ERROR, "Prepare access pixmap failed.\n"); + return ret; + } + + if ((access & SAA_ACCESS_R) != 0 && spix->read_access++ == 0) + map_access = SAA_ACCESS_R; + if ((access & SAA_ACCESS_W) != 0 && spix->write_access++ == 0) + map_access |= SAA_ACCESS_W; + + if (map_access) { + if (spix->auth_loc != saa_loc_override) { + (void)driver->sync_for_cpu(driver, pix, map_access); + spix->addr = driver->map(driver, pix, map_access); + } else + spix->addr = spix->override; + spix->mapped_access |= map_access; + } + + pix->devPrivate.ptr = spix->addr; + return TRUE; +} + +void +saa_finish_access_pixmap(PixmapPtr pix, saa_access_t access) +{ + struct saa_screen_priv *sscreen = saa_screen(pix->drawable.pScreen); + struct saa_driver *driver = sscreen->driver; + struct saa_pixmap *spix = saa_pixmap(pix); + saa_access_t unmap_access = 0; + + if ((access & SAA_ACCESS_R) != 0 && --spix->read_access == 0) + unmap_access = SAA_ACCESS_R; + if ((access & SAA_ACCESS_W) != 0 && --spix->write_access == 0) + unmap_access |= SAA_ACCESS_W; + + if (spix->read_access < 0) + LogMessage(X_ERROR, "Incorrect read access.\n"); + if (spix->write_access < 0) + LogMessage(X_ERROR, "Incorrect write access.\n"); + + if (unmap_access) { + if (spix->auth_loc != saa_loc_override) { + driver->unmap(driver, pix, unmap_access); + driver->release_from_cpu(driver, pix, unmap_access); + } + spix->mapped_access &= ~unmap_access; + } + if (!spix->mapped_access) { + spix->addr = NULL; + pix->devPrivate.ptr = SAA_INVALID_ADDRESS; + } +} + +/* + * Callback that is called after a rendering operation. We try to + * determine whether it's a shadow damage or a hw damage and call the + * driver callback. + */ + +static void +saa_report_damage(DamagePtr damage, RegionPtr reg, void *closure) +{ + PixmapPtr pixmap = (PixmapPtr) closure; + struct saa_pixmap *spix = saa_get_saa_pixmap(pixmap); + struct saa_driver *driver = saa_screen(pixmap->drawable.pScreen)->driver; + + if (spix->read_access || spix->write_access) + LogMessage(X_ERROR, "Damage report inside prepare access.\n"); + + driver->operation_complete(driver, pixmap); + DamageEmpty(damage); +} + +Bool +saa_add_damage(PixmapPtr pixmap) +{ + ScreenPtr pScreen = pixmap->drawable.pScreen; + struct saa_pixmap *spix = saa_get_saa_pixmap(pixmap); + + if (spix->damage) + return TRUE; + + spix->damage = DamageCreate(saa_report_damage, NULL, + DamageReportRawRegion, TRUE, pScreen, pixmap); + if (!spix->damage) + return FALSE; + + DamageRegister(&pixmap->drawable, spix->damage); + DamageSetReportAfterOp(spix->damage, TRUE); + + return TRUE; +} + +static inline RegionPtr +saa_pix_damage_region(struct saa_pixmap *spix) +{ + return (spix->damage ? DamageRegion(spix->damage) : NULL); +} + +Bool +saa_pad_read(DrawablePtr draw) +{ + ScreenPtr pScreen = draw->pScreen; + PixmapPtr pix; + int xp; + int yp; + BoxRec box; + RegionRec entire; + Bool ret; + + (void)pScreen; + pix = saa_get_pixmap(draw, &xp, &yp); + + box.x1 = draw->x + xp; + box.y1 = draw->y + yp; + box.x2 = box.x1 + draw->width; + box.y2 = box.y1 + draw->height; + + REGION_INIT(pScreen, &entire, &box, 1); + ret = saa_prepare_access_pixmap(pix, SAA_ACCESS_R, &entire); + REGION_UNINIT(pScreen, &entire); + return ret; +} + +Bool +saa_pad_read_box(DrawablePtr draw, int x, int y, int w, int h) +{ + ScreenPtr pScreen = draw->pScreen; + PixmapPtr pix; + int xp; + int yp; + BoxRec box; + RegionRec entire; + Bool ret; + + (void)pScreen; + pix = saa_get_pixmap(draw, &xp, &yp); + + box.x1 = x + xp; + box.y1 = y + yp; + box.x2 = box.x1 + w; + box.y2 = box.y1 + h; + + REGION_INIT(pScreen, &entire, &box, 1); + ret = saa_prepare_access_pixmap(pix, SAA_ACCESS_R, &entire); + REGION_UNINIT(pScreen, &entire); + return ret; +} + +/** + * Prepares a drawable destination for access, and maps it read-write. + * If check_read is TRUE, pGC should point to a valid GC. The drawable + * may then be mapped write-only if the pending operation admits. + */ + +Bool +saa_pad_write(DrawablePtr draw, GCPtr pGC, Bool check_read, + saa_access_t * access) +{ + int xp; + int yp; + PixmapPtr pixmap = saa_get_pixmap(draw, &xp, &yp); + struct saa_pixmap *spix = saa_pixmap(pixmap); + + *access = SAA_ACCESS_W; + + /* + * If the to-be-damaged area doesn't depend at all on previous + * rendered contents, we don't need to do any readback. + */ + + if (check_read && !saa_gc_reads_destination(draw, pGC)) + return saa_prepare_access_pixmap(pixmap, *access, NULL); + + *access |= SAA_ACCESS_R; + + /* + * Read back the area to be damaged. + */ + + return saa_prepare_access_pixmap(pixmap, *access, + saa_pix_damage_pending(spix)); +} + +void +saa_fad_read(DrawablePtr draw) +{ + saa_finish_access_pixmap(saa_get_drawable_pixmap(draw), SAA_ACCESS_R); +} + +void +saa_fad_write(DrawablePtr draw, saa_access_t access) +{ + PixmapPtr pix = saa_get_drawable_pixmap(draw); + struct saa_pixmap *spix = saa_pixmap(pix); + + saa_finish_access_pixmap(pix, access); + if (spix->damage) + saa_pixmap_dirty(pix, FALSE, saa_pix_damage_pending(spix)); +} + +Bool +saa_gc_reads_destination(DrawablePtr pDrawable, GCPtr pGC) +{ + return ((pGC->alu != GXcopy && pGC->alu != GXclear && pGC->alu != GXset && + pGC->alu != GXcopyInverted) || pGC->fillStyle == FillStippled || + pGC->clientClipType != CT_NONE || + !SAA_PM_IS_SOLID(pDrawable, pGC->planemask)); +} + +Bool +saa_op_reads_destination(CARD8 op) +{ + /* FALSE (does not read destination) is the list of ops in the protocol + * document with "0" in the "Fb" column and no "Ab" in the "Fa" column. + * That's just Clear and Src. ReduceCompositeOp() will already have + * converted con/disjoint clear/src to Clear or Src. + */ + switch (op) { + case PictOpClear: + case PictOpSrc: + return FALSE; + default: + return TRUE; + } +} + +static void +saa_validate_gc(GCPtr pGC, unsigned long changes, DrawablePtr pDrawable) +{ + /* fbValidateGC will do direct access to pixmaps if the tiling has changed. + * Do a few smart things so fbValidateGC can do it's work. + */ + + ScreenPtr pScreen = pDrawable->pScreen; + struct saa_screen_priv *sscreen = saa_screen(pScreen); + struct saa_gc_priv *sgc = saa_gc(pGC); + PixmapPtr pTile = NULL; + Bool finish_current_tile = FALSE; + + /* Either of these conditions is enough to trigger access to a tile pixmap. */ + /* With pGC->tileIsPixel == 1, you run the risk of dereferencing an invalid tile pixmap pointer. */ + if (pGC->fillStyle == FillTiled + || ((changes & GCTile) && !pGC->tileIsPixel)) { + pTile = pGC->tile.pixmap; + + /* Sometimes tile pixmaps are swapped, you need access to: + * - The current tile if it depth matches. + * - Or the rotated tile if that one matches depth and !(changes & GCTile). + * - Or the current tile pixmap and a newly created one. + */ + if (pTile && pTile->drawable.depth != pDrawable->depth + && !(changes & GCTile)) { + PixmapPtr pRotatedTile = fbGetRotatedPixmap(pGC); + + if (pRotatedTile + && pRotatedTile->drawable.depth == pDrawable->depth) + pTile = pRotatedTile; + else + finish_current_tile = TRUE; /* CreatePixmap will be called. */ + } + } + + if (pGC->stipple && !saa_pad_read(&pGC->stipple->drawable)) { + LogMessage(X_ERROR, "Failed stipple prepareaccess.\n"); + return; + } + + if (pTile && !saa_pad_read(&pTile->drawable)) { + LogMessage(X_ERROR, "Failed stipple prepareaccess.\n"); + goto out_no_tile; + } + + /* Calls to Create/DestroyPixmap have to be identified as special, so + * up sscreen->fallback_count. + */ + + sscreen->fallback_count++; + saa_swap(sgc, pGC, funcs); + (*pGC->funcs->ValidateGC) (pGC, changes, pDrawable); + saa_swap(sgc, pGC, funcs); + + if (finish_current_tile && pGC->tile.pixmap) + saa_fad_write(&pGC->tile.pixmap->drawable, SAA_ACCESS_W); + sscreen->fallback_count--; + + if (pTile) + saa_fad_read(&pTile->drawable); + out_no_tile: + if (pGC->stipple) + saa_fad_read(&pGC->stipple->drawable); +} + +static void +saa_destroy_gc(GCPtr pGC) +{ + struct saa_gc_priv *sgc = saa_gc(pGC); + + saa_swap(sgc, pGC, funcs); + (*pGC->funcs->DestroyGC) (pGC); + saa_swap(sgc, pGC, funcs); +} + +static void +saa_change_gc(GCPtr pGC, unsigned long mask) +{ + struct saa_gc_priv *sgc = saa_gc(pGC); + + saa_swap(sgc, pGC, funcs); + (*pGC->funcs->ChangeGC) (pGC, mask); + saa_swap(sgc, pGC, funcs); +} + +static void +saa_copy_gc(GCPtr pGCSrc, unsigned long mask, GCPtr pGCDst) +{ + struct saa_gc_priv *sgc = saa_gc(pGCDst); + + saa_swap(sgc, pGCDst, funcs); + (*pGCDst->funcs->CopyGC) (pGCSrc, mask, pGCDst); + saa_swap(sgc, pGCDst, funcs); +} + +static void +saa_change_clip(GCPtr pGC, int type, pointer pvalue, int nrects) +{ + struct saa_gc_priv *sgc = saa_gc(pGC); + + saa_swap(sgc, pGC, funcs); + (*pGC->funcs->ChangeClip) (pGC, type, pvalue, nrects); + saa_swap(sgc, pGC, funcs); +} + +static void +saa_copy_clip(GCPtr pGCDst, GCPtr pGCSrc) +{ + struct saa_gc_priv *sgc = saa_gc(pGCDst); + + saa_swap(sgc, pGCDst, funcs); + (*pGCDst->funcs->CopyClip) (pGCDst, pGCSrc); + saa_swap(sgc, pGCDst, funcs); +} + +static void +saa_destroy_clip(GCPtr pGC) +{ + struct saa_gc_priv *sgc = saa_gc(pGC); + + saa_swap(sgc, pGC, funcs); + (*pGC->funcs->DestroyClip) (pGC); + saa_swap(sgc, pGC, funcs); +} + +static GCFuncs saa_gc_funcs = { + saa_validate_gc, + saa_change_gc, + saa_copy_gc, + saa_destroy_gc, + saa_change_clip, + saa_destroy_clip, + saa_copy_clip +}; + +/** + * saa_create_gc makes a new GC and hooks up its funcs handler, so that + * saa_validate_gc() will get called. + */ +int +saa_create_gc(GCPtr pGC) +{ + ScreenPtr pScreen = pGC->pScreen; + struct saa_screen_priv *sscreen = saa_screen(pScreen); + struct saa_gc_priv *sgc = saa_gc(pGC); + Bool ret; + + saa_swap(sscreen, pScreen, CreateGC); + ret = pScreen->CreateGC(pGC); + if (ret) { + saa_wrap(sgc, pGC, funcs, &saa_gc_funcs); + saa_wrap(sgc, pGC, ops, &saa_gc_ops); + } + saa_swap(sscreen, pScreen, CreateGC); + + return ret; +} + +static Bool +saa_prepare_access_window(WindowPtr pWin) +{ + if (pWin->backgroundState == BackgroundPixmap) { + if (!saa_pad_read(&pWin->background.pixmap->drawable)) + return FALSE; + } + + if (pWin->borderIsPixel == FALSE) { + if (!saa_pad_read(&pWin->border.pixmap->drawable)) { + if (pWin->backgroundState == BackgroundPixmap) + saa_fad_read(&pWin->background.pixmap->drawable); + return FALSE; + } + } + return TRUE; +} + +static void +saa_finish_access_window(WindowPtr pWin) +{ + if (pWin->backgroundState == BackgroundPixmap) + saa_fad_read(&pWin->background.pixmap->drawable); + + if (pWin->borderIsPixel == FALSE) + saa_fad_read(&pWin->border.pixmap->drawable); +} + +static Bool +saa_change_window_attributes(WindowPtr pWin, unsigned long mask) +{ + Bool ret; + + if (!saa_prepare_access_window(pWin)) + return FALSE; + ret = fbChangeWindowAttributes(pWin, mask); + saa_finish_access_window(pWin); + return ret; +} + +RegionPtr +saa_bitmap_to_region(PixmapPtr pPix) +{ + RegionPtr ret; + + if (!saa_pad_read(&pPix->drawable)) + return NULL; + ret = fbPixmapToRegion(pPix); + saa_fad_read(&pPix->drawable); + return ret; +} + +void +saa_set_fallback_debug(ScreenPtr screen, Bool enable) +{ + struct saa_screen_priv *sscreen = saa_screen(screen); + + sscreen->fallback_debug = enable; +} + +/** + * saa_close_screen() unwraps its wrapped screen functions and tears down SAA's + * screen private, before calling down to the next CloseScreen. + */ +Bool +saa_close_screen(int i, ScreenPtr pScreen) +{ + struct saa_screen_priv *sscreen = saa_screen(pScreen); + struct saa_driver *driver = sscreen->driver; + + if (pScreen->devPrivate) { + /* Destroy the pixmap created by miScreenInit() *before* + * chaining up as we finalize ourselves here and so this + * is the last chance we have of releasing our resources + * associated with the Pixmap. So do it first. + */ + (void)(*pScreen->DestroyPixmap) (pScreen->devPrivate); + pScreen->devPrivate = NULL; + } + + saa_unwrap(sscreen, pScreen, CloseScreen); + saa_unwrap(sscreen, pScreen, CreateGC); + saa_unwrap(sscreen, pScreen, ChangeWindowAttributes); + saa_unwrap(sscreen, pScreen, CreatePixmap); + saa_unwrap(sscreen, pScreen, DestroyPixmap); + saa_unwrap(sscreen, pScreen, ModifyPixmapHeader); + saa_unwrap(sscreen, pScreen, BitmapToRegion); +#ifdef RENDER + saa_render_takedown(pScreen); +#endif + saa_unaccel_takedown(pScreen); + driver->takedown(driver); + + free(sscreen); + + return (*pScreen->CloseScreen) (i, pScreen); +} + +struct saa_driver * +saa_get_driver(ScreenPtr pScreen) +{ + return saa_screen(pScreen)->driver; +} + +/** + * @param pScreen screen being initialized + * @param pScreenInfo SAA driver record + * + * saa_driver_init sets up SAA given a driver record filled in by the driver. + * pScreenInfo should have been allocated by saa_driver_alloc(). See the + * comments in _SaaDriver for what must be filled in and what is optional. + * + * @return TRUE if SAA was successfully initialized. + */ +Bool +saa_driver_init(ScreenPtr screen, struct saa_driver * saa_driver) +{ + struct saa_screen_priv *sscreen; + + if (!saa_driver) + return FALSE; + + if (saa_driver->saa_major != SAA_VERSION_MAJOR || + saa_driver->saa_minor > SAA_VERSION_MINOR) { + LogMessage(X_ERROR, + "SAA(%d): driver's SAA version requirements " + "(%d.%d) are incompatible with SAA version (%d.%d)\n", + screen->myNum, saa_driver->saa_major, + saa_driver->saa_minor, SAA_VERSION_MAJOR, SAA_VERSION_MINOR); + return FALSE; + } +#if 0 + if (!saa_driver->prepare_solid) { + LogMessage(X_ERROR, + "SAA(%d): saa_driver_t::prepare_solid must be " + "non-NULL\n", screen->myNum); + return FALSE; + } + + if (!saa_driver->prepare_copy) { + LogMessage(X_ERROR, + "SAA(%d): saa_driver_t::prepare_copy must be " + "non-NULL\n", screen->myNum); + return FALSE; + } +#endif +#ifdef SAA_DEVPRIVATEKEYREC + if (!dixRegisterPrivateKey(&saa_screen_index, PRIVATE_SCREEN, 0)) { + LogMessage(X_ERROR, "Failed to register SAA screen private.\n"); + return FALSE; + } + if (!dixRegisterPrivateKey(&saa_pixmap_index, PRIVATE_PIXMAP, + saa_driver->pixmap_size)) { + LogMessage(X_ERROR, "Failed to register SAA pixmap private.\n"); + return FALSE; + } + if (!dixRegisterPrivateKey(&saa_gc_index, PRIVATE_GC, + sizeof(struct saa_gc_priv))) { + LogMessage(X_ERROR, "Failed to register SAA gc private.\n"); + return FALSE; + } +#else + if (!dixRequestPrivate(&saa_screen_index, 0)) { + LogMessage(X_ERROR, "Failed to register SAA screen private.\n"); + return FALSE; + } + if (!dixRequestPrivate(&saa_pixmap_index, saa_driver->pixmap_size)) { + LogMessage(X_ERROR, "Failed to register SAA pixmap private.\n"); + return FALSE; + } + if (!dixRequestPrivate(&saa_gc_index, sizeof(struct saa_gc_priv))) { + LogMessage(X_ERROR, "Failed to register SAA gc private.\n"); + return FALSE; + } +#endif + + sscreen = calloc(1, sizeof(*sscreen)); + + if (!sscreen) { + LogMessage(X_WARNING, + "SAA(%d): Failed to allocate screen private\n", + screen->myNum); + return FALSE; + } + + sscreen->driver = saa_driver; + dixSetPrivate(&screen->devPrivates, &saa_screen_index, sscreen); + + /* + * Replace various fb screen functions + */ + + saa_wrap(sscreen, screen, CloseScreen, saa_close_screen); + saa_wrap(sscreen, screen, CreateGC, saa_create_gc); + saa_wrap(sscreen, screen, ChangeWindowAttributes, + saa_change_window_attributes); + saa_wrap(sscreen, screen, CreatePixmap, saa_create_pixmap); + saa_wrap(sscreen, screen, DestroyPixmap, saa_destroy_pixmap); + saa_wrap(sscreen, screen, ModifyPixmapHeader, saa_modify_pixmap_header); + + saa_wrap(sscreen, screen, BitmapToRegion, saa_bitmap_to_region); + saa_unaccel_setup(screen); +#ifdef RENDER + saa_render_setup(screen); +#endif + + return TRUE; +} + +Bool +saa_resources_init(ScreenPtr screen) +{ +/* if (!saa_glyphs_init(screen)) + return FALSE; +*/ + return TRUE; +} |