splash_bmp.c revision 73857
1/*-
2 * Copyright (c) 1999 Michael Smith <msmith@freebsd.org>
3 * Copyright (c) 1999 Kazutaka YOKOTA <yokota@freebsd.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 * $FreeBSD: head/sys/dev/fb/splash_bmp.c 73857 2001-03-06 03:07:58Z jhb $
28 */
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/kernel.h>
33#include <sys/linker.h>
34#include <sys/fbio.h>
35
36#include <dev/fb/fbreg.h>
37#include <dev/fb/splashreg.h>
38#ifndef PC98
39#include <dev/fb/vgareg.h>
40
41#include <isa/isareg.h>
42#endif
43
44#define FADE_TIMEOUT	15	/* sec */
45#define FADE_LEVELS	10
46
47static int splash_mode = -1;
48static int splash_on = FALSE;
49
50static int bmp_start(video_adapter_t *adp);
51static int bmp_end(video_adapter_t *adp);
52static int bmp_splash(video_adapter_t *adp, int on);
53static int bmp_Init(const char *data, int swidth, int sheight, int sdepth);
54static int bmp_Draw(video_adapter_t *adp);
55
56static splash_decoder_t bmp_decoder = {
57    "splash_bmp", bmp_start, bmp_end, bmp_splash, SPLASH_IMAGE,
58};
59
60SPLASH_DECODER(splash_bmp, bmp_decoder);
61
62static int
63bmp_start(video_adapter_t *adp)
64{
65    /* currently only 256-color modes are supported XXX */
66    static int		modes[] = {
67#ifdef PC98
68    			/*
69			 * As 640x400 doesn't generally look great,
70			 * it's least preferred here.
71			 */
72			M_PC98_PEGC640x400,
73			M_PC98_PEGC640x480,
74			M_PC98_EGC640x400,
75#else
76			M_VESA_CG640x480,
77			M_VESA_CG800x600,
78			M_VESA_CG1024x768,
79			M_CG640x480,
80    			/*
81			 * As 320x200 doesn't generally look great,
82			 * it's least preferred here.
83			 */
84			M_VGA_CG320,
85#endif
86			-1,
87    };
88    video_info_t 	info;
89    int			i;
90
91    if ((bmp_decoder.data == NULL) || (bmp_decoder.data_size <= 0)) {
92	printf("splash_bmp: No bitmap file found\n");
93	return ENODEV;
94    }
95    for (i = 0; modes[i] >= 0; ++i) {
96	if (((*vidsw[adp->va_index]->get_info)(adp, modes[i], &info) == 0)
97	    && (bmp_Init((u_char *)bmp_decoder.data,
98			 info.vi_width, info.vi_height, info.vi_depth) == 0))
99	    break;
100    }
101    splash_mode = modes[i];
102    if (splash_mode < 0)
103	printf("splash_bmp: No appropriate video mode found\n");
104    if (bootverbose)
105	printf("bmp_start(): splash_mode:%d\n", splash_mode);
106    return ((splash_mode < 0) ? ENODEV : 0);
107}
108
109static int
110bmp_end(video_adapter_t *adp)
111{
112    /* nothing to do */
113    return 0;
114}
115
116static int
117bmp_splash(video_adapter_t *adp, int on)
118{
119    static u_char	pal[256*3];
120    static long		time_stamp;
121    u_char		tpal[256*3];
122    static int		fading = TRUE, brightness = FADE_LEVELS;
123    struct timeval	tv;
124    int			i;
125
126    if (on) {
127	if (!splash_on) {
128	    /* set up the video mode and draw something */
129	    if ((*vidsw[adp->va_index]->set_mode)(adp, splash_mode))
130		return 1;
131	    if (bmp_Draw(adp))
132		return 1;
133	    (*vidsw[adp->va_index]->save_palette)(adp, pal);
134	    time_stamp = 0;
135	    splash_on = TRUE;
136	}
137	/*
138	 * This is a kludge to fade the image away.  This section of the
139	 * code takes effect only after the system is completely up.
140	 * FADE_TIMEOUT should be configurable.
141	 */
142	if (!cold) {
143	    getmicrotime(&tv);
144	    if (time_stamp == 0)
145		time_stamp = tv.tv_sec;
146	    if (tv.tv_sec > time_stamp + FADE_TIMEOUT) {
147		if (fading)
148		    if (brightness == 0) {
149			fading = FALSE;
150			brightness++;
151		    }
152		    else brightness--;
153		else
154		    if (brightness == FADE_LEVELS) {
155			fading = TRUE;
156			brightness--;
157		    }
158		    else brightness++;
159		for (i = 0; i < sizeof(pal); ++i) {
160		    tpal[i] = pal[i] * brightness / FADE_LEVELS;
161		}
162		(*vidsw[adp->va_index]->load_palette)(adp, tpal);
163		time_stamp = tv.tv_sec;
164	    }
165	}
166	return 0;
167    } else {
168	/* the video mode will be restored by the caller */
169	splash_on = FALSE;
170	return 0;
171    }
172}
173
174/*
175** Code to handle Microsoft DIB (".BMP") format images.
176**
177** Blame me (msmith@freebsd.org) if this is broken, not Soren.
178*/
179
180typedef struct tagBITMAPFILEHEADER {    /* bmfh */
181    u_short	bfType		__attribute__ ((packed));
182    int		bfSize		__attribute__ ((packed));
183    u_short	bfReserved1	__attribute__ ((packed));
184    u_short	bfReserved2	__attribute__ ((packed));
185    int		bfOffBits	__attribute__ ((packed));
186} BITMAPFILEHEADER;
187
188typedef struct tagBITMAPINFOHEADER {    /* bmih */
189    int		biSize		__attribute__ ((packed));
190    int		biWidth		__attribute__ ((packed));
191    int		biHeight	__attribute__ ((packed));
192    short	biPlanes	__attribute__ ((packed));
193    short	biBitCount	__attribute__ ((packed));
194    int		biCompression	__attribute__ ((packed));
195    int		biSizeImage	__attribute__ ((packed));
196    int		biXPelsPerMeter	__attribute__ ((packed));
197    int		biYPelsPerMeter	__attribute__ ((packed));
198    int		biClrUsed	__attribute__ ((packed));
199    int		biClrImportant	__attribute__ ((packed));
200} BITMAPINFOHEADER;
201
202typedef struct tagRGBQUAD {     /* rgbq */
203    u_char	rgbBlue		__attribute__ ((packed));
204    u_char	rgbGreen	__attribute__ ((packed));
205    u_char	rgbRed		__attribute__ ((packed));
206    u_char	rgbReserved	__attribute__ ((packed));
207} RGBQUAD;
208
209typedef struct tagBITMAPINFO {  /* bmi */
210    BITMAPINFOHEADER	bmiHeader	__attribute__ ((packed));
211    RGBQUAD		bmiColors[256]	__attribute__ ((packed));
212} BITMAPINFO;
213
214typedef struct tagBITMAPF
215{
216    BITMAPFILEHEADER	bmfh	__attribute__ ((packed));
217    BITMAPINFO		bmfi	__attribute__ ((packed));
218} BITMAPF;
219
220#define BI_RGB		0
221#define BI_RLE8		1
222#define BI_RLE4		2
223
224/*
225** all we actually care about the image
226*/
227typedef struct
228{
229    int		width,height;		/* image dimensions */
230    int		swidth,sheight;		/* screen dimensions for the current mode */
231    u_char	depth;			/* image depth (1, 4, 8, 24 bits) */
232    u_char	sdepth;			/* screen depth (1, 4, 8 bpp) */
233    int		ncols;			/* number of colours */
234    u_char	palette[256][3];	/* raw palette data */
235    u_char	format;			/* one of the BI_* constants above */
236    u_char	*data;			/* pointer to the raw data */
237    u_char	*index;			/* running pointer to the data while drawing */
238    u_char	*vidmem;		/* video memory allocated for drawing */
239    video_adapter_t *adp;
240    int		bank;
241#ifdef PC98
242    u_char	prev_val;
243#endif
244} BMP_INFO;
245
246static BMP_INFO bmp_info;
247
248/*
249** bmp_SetPix
250**
251** Given (info), set the pixel at (x),(y) to (val)
252**
253*/
254static void
255bmp_SetPix(BMP_INFO *info, int x, int y, u_char val)
256{
257    int		sofs, bofs;
258    int		newbank;
259
260    /*
261     * range check to avoid explosions
262     */
263    if ((x < 0) || (x >= info->swidth) || (y < 0) || (y >= info->sheight))
264	return;
265
266    /*
267     * calculate offset into video memory;
268     * because 0,0 is bottom-left for DIB, we have to convert.
269     */
270    sofs = ((info->height - (y+1) + (info->sheight - info->height) / 2)
271		* info->adp->va_line_width);
272    x += (info->swidth - info->width) / 2;
273
274    switch(info->sdepth) {
275#ifdef PC98
276    case 4:
277	sofs += (x >> 3);
278	bofs = x & 0x7;				/* offset within byte */
279
280	outb(0x7c, 0x80 | 0x40);	/* GRCG on & RMW mode */
281	if (val != info->prev_val) {
282	    outb(0x7e, (val & 1) ? 0xff : 0);	/* tile B */
283	    outb(0x7e, (val & 2) ? 0xff : 0);	/* tile R */
284	    outb(0x7e, (val & 4) ? 0xff : 0);	/* tile G */
285	    outb(0x7e, (val & 8) ? 0xff : 0);	/* tile I */
286
287	    info->prev_val = val;
288	}
289
290	*(info->vidmem+sofs) = (0x80 >> bofs);	/* write new bit */
291	outb(0x7c, 0);		/* GRCG off */
292	break;
293#else
294    case 4:
295    case 1:
296	/* EGA/VGA planar modes */
297	sofs += (x >> 3);
298	newbank = sofs/info->adp->va_window_size;
299	if (info->bank != newbank) {
300	    (*vidsw[info->adp->va_index]->set_win_org)(info->adp, newbank*info->adp->va_window_size);
301	    info->bank = newbank;
302	}
303	sofs %= info->adp->va_window_size;
304	bofs = x & 0x7;				/* offset within byte */
305	outw(GDCIDX, (0x8000 >> bofs) | 0x08);	/* bit mask */
306	outw(GDCIDX, (val << 8) | 0x00);	/* set/reset */
307	*(info->vidmem + sofs) ^= 0xff;		/* read-modify-write */
308	break;
309#endif
310
311    case 8:
312	sofs += x;
313	newbank = sofs/info->adp->va_window_size;
314	if (info->bank != newbank) {
315	    (*vidsw[info->adp->va_index]->set_win_org)(info->adp, newbank*info->adp->va_window_size);
316	    info->bank = newbank;
317	}
318	sofs %= info->adp->va_window_size;
319	*(info->vidmem+sofs) = val;
320	break;
321    }
322}
323
324/*
325** bmp_DecodeRLE4
326**
327** Given (data) pointing to a line of RLE4-format data and (line) being the starting
328** line onscreen, decode the line.
329*/
330static void
331bmp_DecodeRLE4(BMP_INFO *info, int line)
332{
333    int		count;		/* run count */
334    u_char	val;
335    int		x,y;		/* screen position */
336
337    x = 0;			/* starting position */
338    y = line;
339
340    /* loop reading data */
341    for (;;) {
342	/*
343	 * encoded mode starts with a run length, and then a byte with
344	 * two colour indexes to alternate between for the run
345	 */
346	if (*info->index) {
347	    for (count = 0; count < *info->index; count++, x++) {
348		if (count & 1) {		/* odd count, low nybble */
349		    bmp_SetPix(info, x, y, *(info->index+1) & 0x0f);
350		} else {			/* even count, high nybble */
351		    bmp_SetPix(info, x, y, (*(info->index+1) >>4) & 0x0f);
352		}
353	    }
354	    info->index += 2;
355        /*
356	 * A leading zero is an escape; it may signal the end of the
357	 * bitmap, a cursor move, or some absolute data.
358	 */
359	} else {	/* zero tag may be absolute mode or an escape */
360	    switch (*(info->index+1)) {
361	    case 0:				/* end of line */
362		info->index += 2;
363		return;
364	    case 1:				/* end of bitmap */
365		info->index = NULL;
366		return;
367	    case 2:				/* move */
368		x += *(info->index + 2);	/* new coords */
369		y += *(info->index + 3);
370		info->index += 4;
371		break;
372	    default:				/* literal bitmap data */
373		for (count = 0; count < *(info->index + 1); count++, x++) {
374		    val = *(info->index + 2 + (count / 2));	/* byte with nybbles */
375		    if (count & 1) {
376			val &= 0xf;		/* get low nybble */
377		    } else {
378			val = (val >> 4);	/* get high nybble */
379		    }
380		    bmp_SetPix(info, x, y, val);
381		}
382		/* warning, this depends on integer truncation, do not hand-optimise! */
383		info->index += 2 + ((count + 3) / 4) * 2;
384		break;
385	    }
386	}
387    }
388}
389
390/*
391** bmp_DecodeRLE8
392** Given (data) pointing to a line of RLE8-format data and (line) being the starting
393** line onscreen, decode the line.
394*/
395static void
396bmp_DecodeRLE8(BMP_INFO *info, int line)
397{
398    int		count;		/* run count */
399    int		x,y;		/* screen position */
400
401    x = 0;			/* starting position */
402    y = line;
403
404    /* loop reading data */
405    for(;;) {
406	/*
407	 * encoded mode starts with a run length, and then a byte with
408	 * two colour indexes to alternate between for the run
409	 */
410	if (*info->index) {
411	    for (count = 0; count < *info->index; count++, x++)
412		bmp_SetPix(info, x, y, *(info->index+1));
413	    info->index += 2;
414        /*
415	 * A leading zero is an escape; it may signal the end of the
416	 * bitmap, a cursor move, or some absolute data.
417	 */
418	} else {	/* zero tag may be absolute mode or an escape */
419	    switch(*(info->index+1)) {
420	    case 0:				/* end of line */
421		info->index += 2;
422		return;
423	    case 1:				/* end of bitmap */
424		info->index = NULL;
425		return;
426	    case 2:				/* move */
427		x += *(info->index + 2);	/* new coords */
428		y += *(info->index + 3);
429		info->index += 4;
430		break;
431	    default:				/* literal bitmap data */
432		for (count = 0; count < *(info->index + 1); count++, x++)
433		    bmp_SetPix(info, x, y, *(info->index + 2 + count));
434		/* must be an even count */
435		info->index += 2 + count + (count & 1);
436		break;
437	    }
438	}
439    }
440}
441
442/*
443** bmp_DecodeLine
444**
445** Given (info) pointing to an image being decoded, (line) being the line currently
446** being displayed, decode a line of data.
447*/
448static void
449bmp_DecodeLine(BMP_INFO *info, int line)
450{
451    int		x;
452    u_char	val, mask, *p;
453
454    switch(info->format) {
455    case BI_RGB:
456    	switch(info->depth) {
457	case 8:
458	    for (x = 0; x < info->width; x++, info->index++)
459		bmp_SetPix(info, x, line, *info->index);
460	    info->index += 3 - (--x % 4);
461	    break;
462	case 4:
463	    p = info->index;
464	    for (x = 0; x < info->width; x++) {
465		if (x & 1) {
466		    val = *p & 0xf;	/* get low nybble */
467		    p++;
468		} else {
469		    val = *p >> 4;	/* get high nybble */
470		}
471		bmp_SetPix(info, x, line, val);
472	    }
473	    /* warning, this depends on integer truncation, do not hand-optimise! */
474	    info->index += ((x + 7) / 8) * 4;
475	    break;
476	case 1:
477	    p = info->index;
478	    mask = 0x80;
479	    for (x = 0; x < info->width; x++) {
480		val = (*p & mask) ? 1 : 0;
481		mask >>= 1;
482		if (mask == 0) {
483		    mask = 0x80;
484		    p++;
485		}
486		bmp_SetPix(info, x, line, val);
487	    }
488	    /* warning, this depends on integer truncation, do not hand-optimise! */
489	    info->index += ((x + 31) / 32) * 4;
490	    break;
491	}
492	break;
493    case BI_RLE4:
494	bmp_DecodeRLE4(info, line);
495	break;
496    case BI_RLE8:
497	bmp_DecodeRLE8(info, line);
498	break;
499    }
500}
501
502/*
503** bmp_Init
504**
505** Given a pointer (data) to the image of a BMP file, fill in bmp_info with what
506** can be learnt from it.  Return nonzero if the file isn't usable.
507**
508** Take screen dimensions (swidth), (sheight) and (sdepth) and make sure we
509** can work with these.
510*/
511static int
512bmp_Init(const char *data, int swidth, int sheight, int sdepth)
513{
514    BITMAPF	*bmf = (BITMAPF *)data;
515    int		pind;
516
517    bmp_info.data = NULL;	/* assume setup failed */
518
519    /* check file ID */
520    if (bmf->bmfh.bfType != 0x4d42) {
521	printf("splash_bmp: not a BMP file\n");
522	return(1);		/* XXX check word ordering for big-endian ports? */
523    }
524
525    /* do we understand this bitmap format? */
526    if (bmf->bmfi.bmiHeader.biSize > sizeof(bmf->bmfi.bmiHeader)) {
527	printf("splash_bmp: unsupported BMP format (size=%d)\n",
528		bmf->bmfi.bmiHeader.biSize);
529	return(1);
530    }
531
532    /* save what we know about the screen */
533    bmp_info.swidth = swidth;
534    bmp_info.sheight = sheight;
535    bmp_info.sdepth = sdepth;
536
537    /* where's the data? */
538    bmp_info.data = (u_char *)data + bmf->bmfh.bfOffBits;
539
540    /* image parameters */
541    bmp_info.width = bmf->bmfi.bmiHeader.biWidth;
542    bmp_info.height = bmf->bmfi.bmiHeader.biHeight;
543    bmp_info.depth = bmf->bmfi.bmiHeader.biBitCount;
544    bmp_info.format = bmf->bmfi.bmiHeader.biCompression;
545
546    switch(bmp_info.format) {	/* check compression format */
547    case BI_RGB:
548    case BI_RLE4:
549    case BI_RLE8:
550	break;
551    default:
552	printf("splash_bmp: unsupported compression format\n");
553	return(1);		/* unsupported compression format */
554    }
555
556    /* palette details */
557    bmp_info.ncols = (bmf->bmfi.bmiHeader.biClrUsed);
558    bzero(bmp_info.palette,sizeof(bmp_info.palette));
559    if (bmp_info.ncols == 0) {	/* uses all of them */
560	bmp_info.ncols = 1 << bmf->bmfi.bmiHeader.biBitCount;
561    }
562    if ((bmp_info.height > bmp_info.sheight) ||
563	(bmp_info.width > bmp_info.swidth) ||
564	(bmp_info.ncols > (1 << sdepth))) {
565	if (bootverbose)
566	    printf("splash_bmp: beyond screen capacity (%dx%d, %d colors)\n",
567		   bmp_info.width, bmp_info.height, bmp_info.ncols);
568	return(1);
569    }
570
571    /* read palette */
572    for (pind = 0; pind < bmp_info.ncols; pind++) {
573	bmp_info.palette[pind][0] = bmf->bmfi.bmiColors[pind].rgbRed;
574	bmp_info.palette[pind][1] = bmf->bmfi.bmiColors[pind].rgbGreen;
575	bmp_info.palette[pind][2] = bmf->bmfi.bmiColors[pind].rgbBlue;
576    }
577    return(0);
578}
579
580/*
581** bmp_Draw
582**
583** Render the image.  Return nonzero if that's not possible.
584**
585*/
586static int
587bmp_Draw(video_adapter_t *adp)
588{
589    int		line;
590#if 0
591#ifndef PC98
592    int		i;
593#endif
594#endif
595
596    if (bmp_info.data == NULL) {	/* init failed, do nothing */
597	return(1);
598    }
599
600    /* clear the screen */
601    bmp_info.vidmem = (u_char *)adp->va_window;
602    bmp_info.adp = adp;
603    (*vidsw[adp->va_index]->clear)(adp);
604    (*vidsw[adp->va_index]->set_win_org)(adp, 0);
605    bmp_info.bank = 0;
606
607    /* initialise the info structure for drawing */
608    bmp_info.index = bmp_info.data;
609#ifdef PC98
610    bmp_info.prev_val = 255;
611#endif
612
613    /* set the palette for our image */
614    (*vidsw[adp->va_index]->load_palette)(adp, (u_char *)&bmp_info.palette);
615
616#if 0
617#ifndef PC98
618    /* XXX: this is ugly, but necessary for EGA/VGA 1bpp/4bpp modes */
619    if ((adp->va_type == KD_EGA) || (adp->va_type == KD_VGA)) {
620	inb(adp->va_crtc_addr + 6);		/* reset flip-flop */
621	outb(ATC, 0x14);
622	outb(ATC, 0);
623	for (i = 0; i < 16; ++i) {
624	    outb(ATC, i);
625	    outb(ATC, i);
626	}
627	inb(adp->va_crtc_addr + 6);		/* reset flip-flop */
628	outb(ATC, 0x20);			/* enable palette */
629
630	outw(GDCIDX, 0x0f01);			/* set/reset enable */
631
632	if (bmp_info.sdepth == 1)
633	    outw(TSIDX, 0x0102);		/* unmask plane #0 */
634    }
635#endif
636#endif
637
638    for (line = 0; (line < bmp_info.height) && bmp_info.index; line++) {
639	bmp_DecodeLine(&bmp_info, line);
640    }
641    return(0);
642}
643