syscons(4): Base renderer selection on the new mode's memory model.
[dragonfly.git] / sys / dev / misc / syscons / scvidctl.c
1 /*-
2  * Copyright (c) 1998 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp>
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to The DragonFly Project
6  * by Sascha Wildner <saw@online.de>
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer as
13  *    the first lines of this file unmodified.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * $FreeBSD: src/sys/dev/syscons/scvidctl.c,v 1.19.2.2 2000/05/05 09:16:08 nyan Exp $
30  * $DragonFly: src/sys/dev/misc/syscons/scvidctl.c,v 1.16 2007/08/19 11:39:11 swildner Exp $
31  */
32
33 #include "opt_syscons.h"
34
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/conf.h>
38 #include <sys/signalvar.h>
39 #include <sys/tty.h>
40 #include <sys/kernel.h>
41 #include <sys/thread2.h>
42
43 #include <machine/console.h>
44
45 #include <dev/video/fb/fbreg.h>
46 #include "syscons.h"
47
48 SET_DECLARE(scrndr_set, const sc_renderer_t);
49
50 int
51 sc_set_text_mode(scr_stat *scp, struct tty *tp, int mode, int xsize, int ysize,
52                  int fontsize)
53 {
54     video_info_t info;
55     u_char *font;
56     int prev_ysize;
57     int new_ysize;
58     int error;
59
60     if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, mode, &info))
61         return ENODEV;
62
63     /* adjust argument values */
64     if (fontsize <= 0)
65         fontsize = info.vi_cheight;
66     if (fontsize < 14) {
67         fontsize = 8;
68 #ifndef SC_NO_FONT_LOADING
69         if (!(scp->sc->fonts_loaded & FONT_8))
70             return EINVAL;
71         font = scp->sc->font_8;
72 #else
73         font = NULL;
74 #endif
75     } else if (fontsize >= 16) {
76         fontsize = 16;
77 #ifndef SC_NO_FONT_LOADING
78         if (!(scp->sc->fonts_loaded & FONT_16))
79             return EINVAL;
80         font = scp->sc->font_16;
81 #else
82         font = NULL;
83 #endif
84     } else {
85         fontsize = 14;
86 #ifndef SC_NO_FONT_LOADING
87         if (!(scp->sc->fonts_loaded & FONT_14))
88             return EINVAL;
89         font = scp->sc->font_14;
90 #else
91         font = NULL;
92 #endif
93     }
94     if ((xsize <= 0) || (xsize > info.vi_width))
95         xsize = info.vi_width;
96     if ((ysize <= 0) || (ysize > info.vi_height))
97         ysize = info.vi_height;
98
99     /* stop screen saver, etc */
100     crit_enter();
101     if ((error = sc_clean_up(scp))) {
102         crit_exit();
103         return error;
104     }
105
106     if (sc_render_match(scp, scp->sc->adp->va_name, V_INFO_MM_TEXT) == NULL) {
107         crit_exit();
108         return ENODEV;
109     }
110
111     /* set up scp */
112     new_ysize = 0;
113 #ifndef SC_NO_HISTORY
114     if (scp->history != NULL) {
115         sc_hist_save(scp);
116         new_ysize = sc_vtb_rows(scp->history); 
117     }
118 #endif
119     prev_ysize = scp->ysize;
120     /*
121      * This is a kludge to fend off scrn_update() while we
122      * muck around with scp. XXX
123      */
124     scp->status |= UNKNOWN_MODE | MOUSE_HIDDEN;
125     scp->status &= ~(GRAPHICS_MODE | PIXEL_MODE | MOUSE_VISIBLE);
126     scp->mode = mode;
127     scp->model = V_INFO_MM_TEXT;
128     scp->xsize = xsize;
129     scp->ysize = ysize;
130     scp->xoff = 0;
131     scp->yoff = 0;
132     scp->xpixel = scp->xsize*8;
133     scp->ypixel = scp->ysize*fontsize;
134     scp->font = font;
135     scp->font_size = fontsize;
136
137     /* allocate buffers */
138     sc_alloc_scr_buffer(scp, TRUE, TRUE);
139     sc_init_emulator(scp, NULL);
140 #ifndef SC_NO_CUTPASTE
141     sc_alloc_cut_buffer(scp, FALSE);
142 #endif
143 #ifndef SC_NO_HISTORY
144     sc_alloc_history_buffer(scp, new_ysize, prev_ysize, FALSE);
145 #endif
146     crit_exit();
147
148     if (scp == scp->sc->cur_scp)
149         set_mode(scp);
150     scp->status &= ~UNKNOWN_MODE;
151
152     if (tp == NULL)
153         return 0;
154     DPRINTF(5, ("ws_*size (%d,%d), size (%d,%d)\n",
155         tp->t_winsize.ws_col, tp->t_winsize.ws_row, scp->xsize, scp->ysize));
156     if (tp->t_winsize.ws_col != scp->xsize
157         || tp->t_winsize.ws_row != scp->ysize) {
158         tp->t_winsize.ws_col = scp->xsize;
159         tp->t_winsize.ws_row = scp->ysize;
160         pgsignal(tp->t_pgrp, SIGWINCH, 1);
161     }
162
163     return 0;
164 }
165
166 int
167 sc_set_graphics_mode(scr_stat *scp, struct tty *tp, int mode)
168 {
169 #ifdef SC_NO_MODE_CHANGE
170     return ENODEV;
171 #else
172     video_info_t info;
173     int error;
174
175     if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, mode, &info))
176         return ENODEV;
177
178     /* stop screen saver, etc */
179     crit_enter();
180     if ((error = sc_clean_up(scp))) {
181         crit_exit();
182         return error;
183     }
184
185     if (sc_render_match(scp, scp->sc->adp->va_name, V_INFO_MM_OTHER) == NULL) {
186         crit_exit();
187         return ENODEV;
188     }
189
190     /* set up scp */
191     scp->status |= (UNKNOWN_MODE | GRAPHICS_MODE | MOUSE_HIDDEN);
192     scp->status &= ~(PIXEL_MODE | MOUSE_VISIBLE);
193     scp->mode = mode;
194     scp->model = V_INFO_MM_OTHER;
195     /*
196      * Don't change xsize and ysize; preserve the previous vty
197      * and history buffers.
198      */
199     scp->xoff = 0;
200     scp->yoff = 0;
201     scp->xpixel = info.vi_width;
202     scp->ypixel = info.vi_height;
203     scp->font = NULL;
204     scp->font_size = 0;
205 #ifndef SC_NO_SYSMOUSE
206     /* move the mouse cursor at the center of the screen */
207     sc_mouse_move(scp, scp->xpixel / 2, scp->ypixel / 2);
208 #endif
209     sc_init_emulator(scp, NULL);
210     crit_exit();
211
212     if (scp == scp->sc->cur_scp)
213         set_mode(scp);
214     /* clear_graphics();*/
215     refresh_ega_palette(scp);
216     scp->status &= ~UNKNOWN_MODE;
217
218     if (tp == NULL)
219         return 0;
220     if (tp->t_winsize.ws_xpixel != scp->xpixel
221         || tp->t_winsize.ws_ypixel != scp->ypixel) {
222         tp->t_winsize.ws_xpixel = scp->xpixel;
223         tp->t_winsize.ws_ypixel = scp->ypixel;
224         pgsignal(tp->t_pgrp, SIGWINCH, 1);
225     }
226
227     return 0;
228 #endif /* SC_NO_MODE_CHANGE */
229 }
230
231 int
232 sc_set_pixel_mode(scr_stat *scp, struct tty *tp, int xsize, int ysize, 
233                   int fontsize)
234 {
235 #ifndef SC_PIXEL_MODE
236     return ENODEV;
237 #else
238     video_info_t info;
239     u_char *font;
240     int prev_ysize;
241     int new_ysize;
242     int error;
243
244     if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, scp->mode, &info))
245         return ENODEV;          /* this shouldn't happen */
246
247     /* adjust argument values */
248     if (fontsize <= 0)
249         fontsize = info.vi_cheight;
250     if (fontsize < 14) {
251         fontsize = 8;
252 #ifndef SC_NO_FONT_LOADING
253         if (!(scp->sc->fonts_loaded & FONT_8))
254             return EINVAL;
255         font = scp->sc->font_8;
256 #else
257         font = NULL;
258 #endif
259     } else if (fontsize >= 16) {
260         fontsize = 16;
261 #ifndef SC_NO_FONT_LOADING
262         if (!(scp->sc->fonts_loaded & FONT_16))
263             return EINVAL;
264         font = scp->sc->font_16;
265 #else
266         font = NULL;
267 #endif
268     } else {
269         fontsize = 14;
270 #ifndef SC_NO_FONT_LOADING
271         if (!(scp->sc->fonts_loaded & FONT_14))
272             return EINVAL;
273         font = scp->sc->font_14;
274 #else
275         font = NULL;
276 #endif
277     }
278     if (xsize <= 0)
279         xsize = info.vi_width/8;
280     if (ysize <= 0)
281         ysize = info.vi_height/fontsize;
282
283     if ((info.vi_width < xsize*8) || (info.vi_height < ysize*fontsize))
284         return EINVAL;
285
286     /*
287      * We currently support the following graphic modes:
288      *
289      * - 4 bpp planar modes whose memory size does not exceed 64K
290      * - 15, 16, 24 and 32 bpp direct modes with linear frame buffer
291      */
292
293     if (info.vi_mem_model == V_INFO_MM_PLANAR) {
294         if (info.vi_planes != 4)
295             return ENODEV;
296
297         /*
298          * A memory size >64K requires bank switching to access the entire
299          * screen. XXX
300          */
301
302         if (info.vi_width * info.vi_height / 8 > info.vi_window_size)
303             return ENODEV;
304     } else if (info.vi_mem_model == V_INFO_MM_DIRECT) {
305         if (!(info.vi_flags & V_INFO_LINEAR) &&
306             (info.vi_depth != 15) && (info.vi_depth != 16) &&
307             (info.vi_depth != 24) && (info.vi_depth != 32))
308             return ENODEV;
309     } else
310         return ENODEV;
311
312     /* stop screen saver, etc */
313     crit_enter();
314     if ((error = sc_clean_up(scp))) {
315         crit_exit();
316         return error;
317     }
318
319     if (sc_render_match(scp, scp->sc->adp->va_name, info.vi_mem_model) == NULL) {
320         crit_exit();
321         return ENODEV;
322     }
323
324 #if 0
325     if (scp->tsw)
326         (*scp->tsw->te_term)(scp, scp->ts);
327     scp->tsw = NULL;
328     scp->ts = NULL;
329 #endif
330
331     /* set up scp */
332     new_ysize = 0;
333 #ifndef SC_NO_HISTORY
334     if (scp->history != NULL) {
335         sc_hist_save(scp);
336         new_ysize = sc_vtb_rows(scp->history);
337     }
338 #endif
339     prev_ysize = scp->ysize;
340     scp->status |= (UNKNOWN_MODE | PIXEL_MODE | MOUSE_HIDDEN);
341     scp->status &= ~(GRAPHICS_MODE | MOUSE_VISIBLE);
342     scp->model = info.vi_mem_model;
343     scp->xsize = xsize;
344     scp->ysize = ysize;
345     scp->xoff = (scp->xpixel/8 - xsize)/2;
346     scp->yoff = (scp->ypixel/fontsize - ysize)/2;
347     scp->font = font;
348     scp->font_size = fontsize;
349
350     /* allocate buffers */
351     sc_alloc_scr_buffer(scp, TRUE, TRUE);
352     sc_init_emulator(scp, NULL);
353 #ifndef SC_NO_CUTPASTE
354     sc_alloc_cut_buffer(scp, FALSE);
355 #endif
356 #ifndef SC_NO_HISTORY
357     sc_alloc_history_buffer(scp, new_ysize, prev_ysize, FALSE);
358 #endif
359     crit_exit();
360
361     if (scp == scp->sc->cur_scp) {
362         sc_set_border(scp, scp->border);
363         sc_set_cursor_image(scp);
364     }
365
366     scp->status &= ~UNKNOWN_MODE;
367
368     if (tp == NULL)
369         return 0;
370     if (tp->t_winsize.ws_col != scp->xsize
371         || tp->t_winsize.ws_row != scp->ysize) {
372         tp->t_winsize.ws_col = scp->xsize;
373         tp->t_winsize.ws_row = scp->ysize;
374         pgsignal(tp->t_pgrp, SIGWINCH, 1);
375     }
376
377     return 0;
378 #endif /* SC_PIXEL_MODE */
379 }
380
381 #define fb_ioctl(a, c, d)               \
382         (((a) == NULL) ? ENODEV :       \
383                          (*vidsw[(a)->va_index]->ioctl)((a), (c), (caddr_t)(d)))
384
385 int
386 sc_vid_ioctl(struct tty *tp, u_long cmd, caddr_t data, int flag)
387 {
388     scr_stat *scp;
389     video_adapter_t *adp;
390     video_info_t info;
391     int error;
392
393         KKASSERT(tp->t_dev);
394
395     scp = SC_STAT(tp->t_dev);
396     if (scp == NULL)            /* tp == SC_MOUSE */
397                 return ENOIOCTL;
398     adp = scp->sc->adp;
399     if (adp == NULL)            /* shouldn't happen??? */
400                 return ENODEV;
401
402     switch (cmd) {
403
404     case CONS_CURRENTADP:       /* get current adapter index */
405     case FBIO_ADAPTER:
406         return fb_ioctl(adp, FBIO_ADAPTER, data);
407
408     case CONS_CURRENT:          /* get current adapter type */
409     case FBIO_ADPTYPE:
410         return fb_ioctl(adp, FBIO_ADPTYPE, data);
411
412     case CONS_ADPINFO:          /* adapter information */
413     case FBIO_ADPINFO:
414         if (((video_adapter_info_t *)data)->va_index >= 0) {
415             adp = vid_get_adapter(((video_adapter_info_t *)data)->va_index);
416             if (adp == NULL)
417                 return ENODEV;
418         }
419         return fb_ioctl(adp, FBIO_ADPINFO, data);
420
421     case CONS_GET:              /* get current video mode */
422     case FBIO_GETMODE:
423         *(int *)data = scp->mode;
424         return 0;
425
426 #ifndef SC_NO_MODE_CHANGE
427     case CONS_SET:
428     case FBIO_SETMODE:          /* set video mode */
429         if (!(adp->va_flags & V_ADP_MODECHANGE))
430             return ENODEV;
431         info.vi_mode = *(int *)data;
432         error = fb_ioctl(adp, FBIO_MODEINFO, &info);
433         if (error)
434             return error;
435         if (info.vi_flags & V_INFO_GRAPHICS)
436             return sc_set_graphics_mode(scp, tp, *(int *)data);
437         else
438             return sc_set_text_mode(scp, tp, *(int *)data, 0, 0, 0);
439 #endif /* SC_NO_MODE_CHANGE */
440
441     case CONS_MODEINFO:         /* get mode information */
442     case FBIO_MODEINFO:
443         return fb_ioctl(adp, FBIO_MODEINFO, data);
444
445     case CONS_FINDMODE:         /* find a matching video mode */
446     case FBIO_FINDMODE:
447         return fb_ioctl(adp, FBIO_FINDMODE, data);
448
449     case CONS_SETWINORG:        /* set frame buffer window origin */
450     case FBIO_SETWINORG:
451         if (scp != scp->sc->cur_scp)
452             return ENODEV;      /* XXX */
453         return fb_ioctl(adp, FBIO_SETWINORG, data);
454
455     case FBIO_GETWINORG:        /* get frame buffer window origin */
456         if (scp != scp->sc->cur_scp)
457             return ENODEV;      /* XXX */
458         return fb_ioctl(adp, FBIO_GETWINORG, data);
459
460     case FBIO_GETDISPSTART:
461     case FBIO_SETDISPSTART:
462     case FBIO_GETLINEWIDTH:
463     case FBIO_SETLINEWIDTH:
464         if (scp != scp->sc->cur_scp)
465             return ENODEV;      /* XXX */
466         return fb_ioctl(adp, cmd, data);
467
468     case FBIO_GETPALETTE:
469     case FBIO_SETPALETTE:
470     case FBIOPUTCMAP:
471     case FBIOGETCMAP:
472     case FBIOGTYPE:
473     case FBIOGATTR:
474     case FBIOSVIDEO:
475     case FBIOGVIDEO:
476     case FBIOSCURSOR:
477     case FBIOGCURSOR:
478     case FBIOSCURPOS:
479     case FBIOGCURPOS:
480     case FBIOGCURMAX:
481         if (scp != scp->sc->cur_scp)
482             return ENODEV;      /* XXX */
483         return fb_ioctl(adp, cmd, data);
484
485     case KDSETMODE:             /* set current mode of this (virtual) console */
486         switch (*(int *)data) {
487         case KD_TEXT:           /* switch to TEXT (known) mode */
488             /*
489              * If scp->mode is of graphics modes, we don't know which
490              * text mode to switch back to...
491              */
492             if (scp->status & GRAPHICS_MODE)
493                 return EINVAL;
494             /* restore fonts & palette ! */
495 #if 0
496 #ifndef SC_NO_FONT_LOADING
497             if (ISFONTAVAIL(adp->va_flags) 
498                 && !(scp->status & (GRAPHICS_MODE | PIXEL_MODE)))
499                 /*
500                  * FONT KLUDGE
501                  * Don't load fonts for now... XXX
502                  */
503                 if (scp->sc->fonts_loaded & FONT_8)
504                     sc_load_font(scp, 0, 8, scp->sc->font_8, 0, 256);
505                 if (scp->sc->fonts_loaded & FONT_14)
506                     sc_load_font(scp, 0, 14, scp->sc->font_14, 0, 256);
507                 if (scp->sc->fonts_loaded & FONT_16)
508                     sc_load_font(scp, 0, 16, scp->sc->font_16, 0, 256);
509             }
510 #endif /* SC_NO_FONT_LOADING */
511 #endif
512
513 #ifndef SC_NO_PALETTE_LOADING
514             load_palette(adp, scp->sc->palette);
515 #endif
516
517             /* move hardware cursor out of the way */
518             (*vidsw[adp->va_index]->set_hw_cursor)(adp, -1, -1);
519             /* FALL THROUGH */
520
521         case KD_TEXT1:          /* switch to TEXT (known) mode */
522             /*
523              * If scp->mode is of graphics modes, we don't know which
524              * text/pixel mode to switch back to...
525              */
526             if (scp->status & GRAPHICS_MODE)
527                 return EINVAL;
528             crit_enter();
529             if ((error = sc_clean_up(scp))) {
530                 crit_exit();
531                 return error;
532             }
533             scp->status |= UNKNOWN_MODE | MOUSE_HIDDEN;
534             crit_exit();
535             /* no restore fonts & palette */
536             if (scp == scp->sc->cur_scp)
537                 set_mode(scp);
538             sc_clear_screen(scp);
539             scp->status &= ~UNKNOWN_MODE;
540             return 0;
541
542 #ifdef SC_PIXEL_MODE
543         case KD_PIXEL:          /* pixel (raster) display */
544             if (!(scp->status & (GRAPHICS_MODE | PIXEL_MODE)))
545                 return EINVAL;
546             if (scp->status & GRAPHICS_MODE)
547                 return sc_set_pixel_mode(scp, tp, scp->xsize, scp->ysize, 
548                                          scp->font_size);
549             crit_enter();
550             if ((error = sc_clean_up(scp))) {
551                 crit_exit();
552                 return error;
553             }
554             scp->status |= (UNKNOWN_MODE | PIXEL_MODE | MOUSE_HIDDEN);
555             crit_exit();
556             if (scp == scp->sc->cur_scp) {
557                 set_mode(scp);
558 #ifndef SC_NO_PALETTE_LOADING
559                 load_palette(adp, scp->sc->palette);
560 #endif
561             }
562             sc_clear_screen(scp);
563             scp->status &= ~UNKNOWN_MODE;
564             return 0;
565 #endif /* SC_PIXEL_MODE */
566
567         case KD_GRAPHICS:       /* switch to GRAPHICS (unknown) mode */
568             crit_enter();
569             if ((error = sc_clean_up(scp))) {
570                 crit_exit();
571                 return error;
572             }
573             scp->status |= UNKNOWN_MODE | MOUSE_HIDDEN;
574             crit_exit();
575             return 0;
576
577         default:
578             return EINVAL;
579         }
580         /* NOT REACHED */
581
582 #ifdef SC_PIXEL_MODE
583     case KDRASTER:              /* set pixel (raster) display mode */
584         if (ISUNKNOWNSC(scp) || ISTEXTSC(scp))
585             return ENODEV;
586         return sc_set_pixel_mode(scp, tp, ((int *)data)[0], ((int *)data)[1], 
587                                  ((int *)data)[2]);
588 #endif /* SC_PIXEL_MODE */
589
590     case KDGETMODE:             /* get current mode of this (virtual) console */
591         /* 
592          * From the user program's point of view, KD_PIXEL is the same 
593          * as KD_TEXT... 
594          */
595         *data = ISGRAPHSC(scp) ? KD_GRAPHICS : KD_TEXT;
596         return 0;
597
598     case KDSBORDER:             /* set border color of this (virtual) console */
599         scp->border = *data;
600         if (scp == scp->sc->cur_scp)
601             sc_set_border(scp, scp->border);
602         return 0;
603     }
604
605     return ENOIOCTL;
606 }
607
608 static LIST_HEAD(, sc_renderer) sc_rndr_list = 
609         LIST_HEAD_INITIALIZER(sc_rndr_list);
610
611 int
612 sc_render_add(sc_renderer_t *rndr)
613 {
614         LIST_INSERT_HEAD(&sc_rndr_list, rndr, link);
615         return 0;
616 }
617
618 int
619 sc_render_remove(sc_renderer_t *rndr)
620 {
621         /*
622         LIST_REMOVE(rndr, link);
623         */
624         return EBUSY;   /* XXX */
625 }
626
627 sc_rndr_sw_t *
628 sc_render_match(scr_stat *scp, char *name, int model)
629 {
630         const sc_renderer_t **list;
631         const sc_renderer_t *p;
632
633         if (!LIST_EMPTY(&sc_rndr_list)) {
634                 LIST_FOREACH(p, &sc_rndr_list, link) {
635                         if ((strcmp(p->name, name) == 0) &&
636                             (model == p->model)) {
637                                 scp->status &=
638                                     ~(VR_CURSOR_ON | VR_CURSOR_BLINK);
639                                 return p->rndrsw;
640                         }
641                 }
642         } else {
643                 SET_FOREACH(list, scrndr_set) {
644                         p = *list;
645                         if ((strcmp(p->name, name) == 0) &&
646                             (model == p->model)) {
647                                 scp->status &=
648                                     ~(VR_CURSOR_ON | VR_CURSOR_BLINK);
649                                 return p->rndrsw;
650                         }
651                 }
652         }
653
654         return NULL;
655 }