[BACK]Return to rk_drm.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / sys / arch / arm / rockchip

Annotation of src/sys/arch/arm/rockchip/rk_drm.c, Revision 1.9

1.9     ! riastrad    1: /* $NetBSD: rk_drm.c,v 1.8 2021/12/19 11:00:46 riastradh Exp $ */
1.1       jmcneill    2:
                      3: /*-
                      4:  * Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca>
                      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 ``AS IS'' AND ANY EXPRESS OR
                     17:  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
                     18:  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
                     19:  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
                     20:  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
                     21:  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
                     22:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
                     23:  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
                     24:  * 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>
1.9     ! riastrad   30: __KERNEL_RCSID(0, "$NetBSD: rk_drm.c,v 1.8 2021/12/19 11:00:46 riastradh Exp $");
1.1       jmcneill   31:
                     32: #include <sys/param.h>
                     33: #include <sys/bus.h>
1.9     ! riastrad   34: #include <sys/conf.h>
1.1       jmcneill   35: #include <sys/device.h>
                     36: #include <sys/intr.h>
1.9     ! riastrad   37: #include <sys/kernel.h>
1.1       jmcneill   38: #include <sys/systm.h>
                     39:
1.9     ! riastrad   40: #include <uvm/uvm_device.h>
1.1       jmcneill   41: #include <uvm/uvm_extern.h>
                     42: #include <uvm/uvm_object.h>
1.9     ! riastrad   43:
        !            44: #include <dev/fdt/fdt_port.h>
        !            45: #include <dev/fdt/fdtvar.h>
        !            46:
        !            47: #include <arm/rockchip/rk_drm.h>
1.1       jmcneill   48:
1.8       riastrad   49: #include <drm/drm_auth.h>
1.9     ! riastrad   50: #include <drm/drm_crtc_helper.h>
1.8       riastrad   51: #include <drm/drm_drv.h>
1.1       jmcneill   52: #include <drm/drm_fb_helper.h>
1.8       riastrad   53: #include <drm/drm_fourcc.h>
                     54: #include <drm/drm_vblank.h>
1.1       jmcneill   55:
                     56: #define        RK_DRM_MAX_WIDTH        3840
                     57: #define        RK_DRM_MAX_HEIGHT       2160
                     58:
                     59: static TAILQ_HEAD(, rk_drm_ports) rk_drm_ports =
                     60:     TAILQ_HEAD_INITIALIZER(rk_drm_ports);
                     61:
1.4       thorpej    62: static const struct device_compatible_entry compat_data[] = {
                     63:        { .compat = "rockchip,display-subsystem" },
                     64:        DEVICE_COMPAT_EOL
1.1       jmcneill   65: };
                     66:
                     67: static const char * fb_compatible[] = {
                     68:        "simple-framebuffer",
                     69:        NULL
                     70: };
                     71:
                     72: static int     rk_drm_match(device_t, cfdata_t, void *);
                     73: static void    rk_drm_attach(device_t, device_t, void *);
                     74:
                     75: static void    rk_drm_init(device_t);
                     76: static vmem_t  *rk_drm_alloc_cma_pool(struct drm_device *, size_t);
                     77:
                     78: static int     rk_drm_set_busid(struct drm_device *, struct drm_master *);
                     79:
                     80: static uint32_t        rk_drm_get_vblank_counter(struct drm_device *, unsigned int);
                     81: static int     rk_drm_enable_vblank(struct drm_device *, unsigned int);
                     82: static void    rk_drm_disable_vblank(struct drm_device *, unsigned int);
                     83:
                     84: static int     rk_drm_load(struct drm_device *, unsigned long);
1.8       riastrad   85: static void    rk_drm_unload(struct drm_device *);
1.1       jmcneill   86:
                     87: static struct drm_driver rk_drm_driver = {
1.8       riastrad   88:        .driver_features = DRIVER_MODESET | DRIVER_GEM,
1.1       jmcneill   89:        .dev_priv_size = 0,
                     90:        .load = rk_drm_load,
                     91:        .unload = rk_drm_unload,
                     92:
                     93:        .gem_free_object = drm_gem_cma_free_object,
                     94:        .mmap_object = drm_gem_or_legacy_mmap_object,
                     95:        .gem_uvm_ops = &drm_gem_cma_uvm_ops,
                     96:
                     97:        .dumb_create = drm_gem_cma_dumb_create,
                     98:        .dumb_destroy = drm_gem_dumb_destroy,
                     99:
                    100:        .get_vblank_counter = rk_drm_get_vblank_counter,
                    101:        .enable_vblank = rk_drm_enable_vblank,
                    102:        .disable_vblank = rk_drm_disable_vblank,
                    103:
                    104:        .name = DRIVER_NAME,
                    105:        .desc = DRIVER_DESC,
                    106:        .date = DRIVER_DATE,
                    107:        .major = DRIVER_MAJOR,
                    108:        .minor = DRIVER_MINOR,
                    109:        .patchlevel = DRIVER_PATCHLEVEL,
                    110:
                    111:        .set_busid = rk_drm_set_busid,
                    112: };
                    113:
                    114: CFATTACH_DECL_NEW(rk_drm, sizeof(struct rk_drm_softc),
                    115:        rk_drm_match, rk_drm_attach, NULL, NULL);
                    116:
                    117: static int
                    118: rk_drm_match(device_t parent, cfdata_t cf, void *aux)
                    119: {
                    120:        struct fdt_attach_args * const faa = aux;
                    121:
1.4       thorpej   122:        return of_compatible_match(faa->faa_phandle, compat_data);
1.1       jmcneill  123: }
                    124:
                    125: static void
                    126: rk_drm_attach(device_t parent, device_t self, void *aux)
                    127: {
                    128:        struct rk_drm_softc * const sc = device_private(self);
                    129:        struct fdt_attach_args * const faa = aux;
                    130:        struct drm_driver * const driver = &rk_drm_driver;
                    131:        prop_dictionary_t dict = device_properties(self);
                    132:        bool is_disabled;
                    133:
                    134:        sc->sc_dev = self;
                    135:        sc->sc_dmat = faa->faa_dmat;
                    136:        sc->sc_bst = faa->faa_bst;
                    137:        sc->sc_phandle = faa->faa_phandle;
                    138:
                    139:        aprint_naive("\n");
                    140:
                    141:        if (prop_dictionary_get_bool(dict, "disabled", &is_disabled) && is_disabled) {
                    142:                aprint_normal(": (disabled)\n");
                    143:                return;
                    144:        }
                    145:
                    146:        aprint_normal("\n");
                    147:
                    148:        sc->sc_ddev = drm_dev_alloc(driver, sc->sc_dev);
                    149:        if (sc->sc_ddev == NULL) {
                    150:                aprint_error_dev(self, "couldn't allocate DRM device\n");
                    151:                return;
                    152:        }
                    153:        sc->sc_ddev->dev_private = sc;
                    154:        sc->sc_ddev->bst = sc->sc_bst;
                    155:        sc->sc_ddev->bus_dmat = sc->sc_dmat;
                    156:        sc->sc_ddev->dmat = sc->sc_ddev->bus_dmat;
                    157:        sc->sc_ddev->dmat_subregion_p = false;
                    158:
                    159:        fdt_remove_bycompat(fb_compatible);
                    160:
                    161:        config_defer(self, rk_drm_init);
                    162: }
                    163:
                    164: static void
                    165: rk_drm_init(device_t dev)
                    166: {
                    167:        struct rk_drm_softc * const sc = device_private(dev);
                    168:        struct drm_driver * const driver = &rk_drm_driver;
                    169:        int error;
                    170:
                    171:        error = -drm_dev_register(sc->sc_ddev, 0);
                    172:        if (error) {
                    173:                aprint_error_dev(dev, "couldn't register DRM device: %d\n",
                    174:                    error);
                    175:                return;
                    176:        }
                    177:
                    178:        aprint_normal_dev(dev, "initialized %s %d.%d.%d %s on minor %d\n",
                    179:            driver->name, driver->major, driver->minor, driver->patchlevel,
                    180:            driver->date, sc->sc_ddev->primary->index);
                    181: }
                    182:
                    183: static vmem_t *
                    184: rk_drm_alloc_cma_pool(struct drm_device *ddev, size_t cma_size)
                    185: {
                    186:        struct rk_drm_softc * const sc = rk_drm_private(ddev);
                    187:        bus_dma_segment_t segs[1];
                    188:        int nsegs;
                    189:        int error;
                    190:
                    191:        error = bus_dmamem_alloc(sc->sc_dmat, cma_size, PAGE_SIZE, 0,
                    192:            segs, 1, &nsegs, BUS_DMA_NOWAIT);
                    193:        if (error) {
                    194:                aprint_error_dev(sc->sc_dev, "couldn't allocate CMA pool\n");
                    195:                return NULL;
                    196:        }
                    197:
                    198:        return vmem_create("rkdrm", segs[0].ds_addr, segs[0].ds_len,
                    199:            PAGE_SIZE, NULL, NULL, NULL, 0, VM_SLEEP, IPL_NONE);
                    200: }
                    201:
                    202: static int
                    203: rk_drm_set_busid(struct drm_device *ddev, struct drm_master *master)
                    204: {
                    205:        struct rk_drm_softc * const sc = rk_drm_private(ddev);
                    206:        char id[32];
                    207:
                    208:        snprintf(id, sizeof(id), "platform:rk:%u", device_unit(sc->sc_dev));
                    209:
                    210:        master->unique = kzalloc(strlen(id) + 1, GFP_KERNEL);
                    211:        if (master->unique == NULL)
                    212:                return -ENOMEM;
                    213:        strcpy(master->unique, id);
                    214:        master->unique_len = strlen(master->unique);
                    215:
                    216:        return 0;
                    217: }
                    218:
                    219: static int
                    220: rk_drm_fb_create_handle(struct drm_framebuffer *fb,
                    221:     struct drm_file *file, unsigned int *handle)
                    222: {
                    223:        struct rk_drm_framebuffer *sfb = to_rk_drm_framebuffer(fb);
                    224:
                    225:        return drm_gem_handle_create(file, &sfb->obj->base, handle);
                    226: }
                    227:
                    228: static void
                    229: rk_drm_fb_destroy(struct drm_framebuffer *fb)
                    230: {
                    231:        struct rk_drm_framebuffer *sfb = to_rk_drm_framebuffer(fb);
                    232:
                    233:        drm_framebuffer_cleanup(fb);
1.8       riastrad  234:        drm_gem_object_put_unlocked(&sfb->obj->base);
1.1       jmcneill  235:        kmem_free(sfb, sizeof(*sfb));
                    236: }
                    237:
                    238: static const struct drm_framebuffer_funcs rk_drm_framebuffer_funcs = {
                    239:        .create_handle = rk_drm_fb_create_handle,
                    240:        .destroy = rk_drm_fb_destroy,
                    241: };
                    242:
                    243: static struct drm_framebuffer *
                    244: rk_drm_fb_create(struct drm_device *ddev, struct drm_file *file,
1.8       riastrad  245:     const struct drm_mode_fb_cmd2 *cmd)
1.1       jmcneill  246: {
                    247:        struct rk_drm_framebuffer *fb;
                    248:        struct drm_gem_object *gem_obj;
                    249:        int error;
                    250:
                    251:        if (cmd->flags)
                    252:                return NULL;
                    253:
1.8       riastrad  254:        gem_obj = drm_gem_object_lookup(file, cmd->handles[0]);
1.1       jmcneill  255:        if (gem_obj == NULL)
                    256:                return NULL;
                    257:
                    258:        fb = kmem_zalloc(sizeof(*fb), KM_SLEEP);
                    259:        fb->obj = to_drm_gem_cma_obj(gem_obj);
                    260:        fb->base.pitches[0] = cmd->pitches[0];
                    261:        fb->base.pitches[1] = cmd->pitches[1];
                    262:        fb->base.pitches[2] = cmd->pitches[2];
                    263:        fb->base.offsets[0] = cmd->offsets[0];
                    264:        fb->base.offsets[1] = cmd->offsets[2];
                    265:        fb->base.offsets[2] = cmd->offsets[1];
                    266:        fb->base.width = cmd->width;
                    267:        fb->base.height = cmd->height;
1.8       riastrad  268:        fb->base.format = drm_format_info(cmd->pixel_format);
1.1       jmcneill  269:
                    270:        error = drm_framebuffer_init(ddev, &fb->base, &rk_drm_framebuffer_funcs);
                    271:        if (error != 0)
                    272:                goto dealloc;
                    273:
                    274:        return &fb->base;
                    275:
                    276: dealloc:
                    277:        drm_framebuffer_cleanup(&fb->base);
                    278:        kmem_free(fb, sizeof(*fb));
1.8       riastrad  279:        drm_gem_object_put_unlocked(gem_obj);
1.1       jmcneill  280:
                    281:        return NULL;
                    282: }
                    283:
                    284: static struct drm_mode_config_funcs rk_drm_mode_config_funcs = {
                    285:        .fb_create = rk_drm_fb_create,
                    286: };
                    287:
                    288: static int
                    289: rk_drm_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes)
                    290: {
                    291:        struct rk_drm_softc * const sc = rk_drm_private(helper->dev);
                    292:        struct drm_device *ddev = helper->dev;
                    293:        struct rk_drm_framebuffer *sfb = to_rk_drm_framebuffer(helper->fb);
                    294:        struct drm_framebuffer *fb = helper->fb;
                    295:        struct rk_drmfb_attach_args sfa;
                    296:        size_t cma_size;
                    297:        int error;
                    298:
                    299:        const u_int width = sizes->surface_width;
                    300:        const u_int height = sizes->surface_height;
                    301:        const u_int pitch = width * (32 / 8);
                    302:
                    303:        const size_t size = roundup(height * pitch, PAGE_SIZE);
                    304:
                    305:        /* Reserve enough memory for the FB console plus a 4K plane, rounded to 1MB */
                    306:        cma_size = size;
                    307:        cma_size += (RK_DRM_MAX_WIDTH * RK_DRM_MAX_HEIGHT * 4);
                    308:        cma_size = roundup(cma_size, 1024 * 1024);
                    309:        sc->sc_ddev->cma_pool = rk_drm_alloc_cma_pool(sc->sc_ddev, cma_size);
                    310:        if (sc->sc_ddev->cma_pool != NULL)
                    311:                aprint_normal_dev(sc->sc_dev, "reserved %u MB DRAM for CMA\n",
                    312:                    (u_int)(cma_size / (1024 * 1024)));
                    313:
                    314:        sfb->obj = drm_gem_cma_create(ddev, size);
                    315:        if (sfb->obj == NULL) {
                    316:                DRM_ERROR("failed to allocate memory for framebuffer\n");
                    317:                return -ENOMEM;
                    318:        }
                    319:
                    320:        fb->pitches[0] = pitch;
                    321:        fb->offsets[0] = 0;
                    322:        fb->width = width;
                    323:        fb->height = height;
1.6       mrg       324: #ifdef __ARM_BIG_ENDIAN
1.8       riastrad  325:        fb->format = drm_format_info(DRM_FORMAT_BGRX8888);
1.6       mrg       326: #else
1.8       riastrad  327:        fb->format = drm_format_info(DRM_FORMAT_XRGB8888);
1.6       mrg       328: #endif
1.1       jmcneill  329:
                    330:        error = drm_framebuffer_init(ddev, fb, &rk_drm_framebuffer_funcs);
                    331:        if (error != 0) {
                    332:                DRM_ERROR("failed to initialize framebuffer\n");
                    333:                return error;
                    334:        }
                    335:
                    336:        memset(&sfa, 0, sizeof(sfa));
                    337:        sfa.sfa_drm_dev = ddev;
                    338:        sfa.sfa_fb_helper = helper;
                    339:        sfa.sfa_fb_sizes = *sizes;
                    340:        sfa.sfa_fb_bst = sc->sc_bst;
                    341:        sfa.sfa_fb_dmat = sc->sc_dmat;
                    342:        sfa.sfa_fb_linebytes = helper->fb->pitches[0];
                    343:
1.5       thorpej   344:        helper->fbdev = config_found(ddev->dev, &sfa, NULL,
1.7       thorpej   345:            CFARGS(.iattr = "rkfbbus"));
1.1       jmcneill  346:        if (helper->fbdev == NULL) {
                    347:                DRM_ERROR("unable to attach framebuffer\n");
                    348:                return -ENXIO;
                    349:        }
                    350:
                    351:        return 0;
                    352: }
                    353:
                    354: static struct drm_fb_helper_funcs rk_drm_fb_helper_funcs = {
                    355:        .fb_probe = rk_drm_fb_probe,
                    356: };
                    357:
                    358: static int
                    359: rk_drm_load(struct drm_device *ddev, unsigned long flags)
                    360: {
                    361:        struct rk_drm_softc * const sc = rk_drm_private(ddev);
                    362:        struct rk_drm_ports *sport;
                    363:        struct rk_drm_fbdev *fbdev;
                    364:        struct fdt_endpoint *ep;
                    365:        const u_int *data;
                    366:        int datalen, error, num_crtc, ep_index;
                    367:
                    368:        drm_mode_config_init(ddev);
                    369:        ddev->mode_config.min_width = 0;
                    370:        ddev->mode_config.min_height = 0;
                    371:        ddev->mode_config.max_width = RK_DRM_MAX_WIDTH;
                    372:        ddev->mode_config.max_height = RK_DRM_MAX_HEIGHT;
                    373:        ddev->mode_config.funcs = &rk_drm_mode_config_funcs;
                    374:
                    375:        num_crtc = 0;
                    376:        data = fdtbus_get_prop(sc->sc_phandle, "ports", &datalen);
                    377:        while (datalen >= 4) {
                    378:                const int crtc_phandle = fdtbus_get_phandle_from_native(be32dec(data));
                    379:
                    380:                TAILQ_FOREACH(sport, &rk_drm_ports, entries)
                    381:                        if (sport->phandle == crtc_phandle && sport->ddev == NULL) {
                    382:                                sport->ddev = ddev;
                    383:                                for (ep_index = 0; (ep = fdt_endpoint_get_from_index(sport->port, 0, ep_index)) != NULL; ep_index++) {
                    384:                                        error = fdt_endpoint_activate_direct(ep, true);
                    385:                                        if (error != 0)
                    386:                                                aprint_debug_dev(sc->sc_dev,
                    387:                                                    "failed to activate endpoint %d: %d\n",
                    388:                                                    ep_index, error);
                    389:                                }
                    390:                                num_crtc++;
                    391:                        }
                    392:
                    393:                datalen -= 4;
                    394:                data++;
                    395:        }
                    396:
                    397:        if (num_crtc == 0) {
                    398:                aprint_error_dev(sc->sc_dev, "no display interface ports configured\n");
1.3       mrg       399:                error = ENXIO;
                    400:                goto drmerr;
1.1       jmcneill  401:        }
                    402:
                    403:        fbdev = kmem_zalloc(sizeof(*fbdev), KM_SLEEP);
                    404:
                    405:        drm_fb_helper_prepare(ddev, &fbdev->helper, &rk_drm_fb_helper_funcs);
                    406:
1.8       riastrad  407:        error = drm_fb_helper_init(ddev, &fbdev->helper, num_crtc);
1.1       jmcneill  408:        if (error)
1.3       mrg       409:                goto allocerr;
1.1       jmcneill  410:
                    411:        fbdev->helper.fb = kmem_zalloc(sizeof(struct rk_drm_framebuffer), KM_SLEEP);
                    412:
                    413:        drm_fb_helper_single_add_all_connectors(&fbdev->helper);
                    414:
                    415:        drm_helper_disable_unused_functions(ddev);
                    416:
                    417:        drm_fb_helper_initial_config(&fbdev->helper, 32);
                    418:
                    419:        /* XXX */
                    420:        ddev->irq_enabled = true;
                    421:        drm_vblank_init(ddev, num_crtc);
                    422:
                    423:        return 0;
                    424:
1.3       mrg       425: allocerr:
                    426:        kmem_free(fbdev, sizeof(*fbdev));
1.1       jmcneill  427: drmerr:
                    428:        drm_mode_config_cleanup(ddev);
                    429:
                    430:        return error;
                    431: }
                    432:
                    433: static uint32_t
                    434: rk_drm_get_vblank_counter(struct drm_device *ddev, unsigned int crtc)
                    435: {
                    436:        struct rk_drm_softc * const sc = rk_drm_private(ddev);
                    437:
                    438:        if (crtc >= __arraycount(sc->sc_vbl))
                    439:                return 0;
                    440:
                    441:        if (sc->sc_vbl[crtc].get_vblank_counter == NULL)
                    442:                return 0;
                    443:
                    444:        return sc->sc_vbl[crtc].get_vblank_counter(sc->sc_vbl[crtc].priv);
                    445: }
                    446:
                    447: static int
                    448: rk_drm_enable_vblank(struct drm_device *ddev, unsigned int crtc)
                    449: {
                    450:        struct rk_drm_softc * const sc = rk_drm_private(ddev);
                    451:
                    452:        if (crtc >= __arraycount(sc->sc_vbl))
                    453:                return 0;
                    454:
                    455:        if (sc->sc_vbl[crtc].enable_vblank == NULL)
                    456:                return 0;
                    457:
                    458:        sc->sc_vbl[crtc].enable_vblank(sc->sc_vbl[crtc].priv);
                    459:
                    460:        return 0;
                    461: }
                    462:
                    463: static void
                    464: rk_drm_disable_vblank(struct drm_device *ddev, unsigned int crtc)
                    465: {
                    466:        struct rk_drm_softc * const sc = rk_drm_private(ddev);
                    467:
                    468:        if (crtc >= __arraycount(sc->sc_vbl))
                    469:                return;
                    470:
                    471:        if (sc->sc_vbl[crtc].disable_vblank == NULL)
                    472:                return;
                    473:
                    474:        sc->sc_vbl[crtc].disable_vblank(sc->sc_vbl[crtc].priv);
                    475: }
                    476:
1.8       riastrad  477: static void
1.1       jmcneill  478: rk_drm_unload(struct drm_device *ddev)
                    479: {
                    480:        drm_mode_config_cleanup(ddev);
                    481: }
                    482:
                    483: int
                    484: rk_drm_register_port(int phandle, struct fdt_device_ports *port)
                    485: {
                    486:        struct rk_drm_ports *sport;
                    487:
                    488:        sport = kmem_zalloc(sizeof(*sport), KM_SLEEP);
                    489:        sport->phandle = phandle;
                    490:        sport->port = port;
                    491:        sport->ddev = NULL;
                    492:        TAILQ_INSERT_TAIL(&rk_drm_ports, sport, entries);
                    493:
                    494:        return 0;
                    495: }
                    496:
                    497: struct drm_device *
                    498: rk_drm_port_device(struct fdt_device_ports *port)
                    499: {
                    500:        struct rk_drm_ports *sport;
                    501:
                    502:        TAILQ_FOREACH(sport, &rk_drm_ports, entries)
                    503:                if (sport->port == port)
                    504:                        return sport->ddev;
                    505:
                    506:        return NULL;
                    507: }

CVSweb <webmaster@jp.NetBSD.org>