Commit | Line | Data |
---|---|---|
a09e091a JB |
1 | |
2 | /* | |
3 | * XFree86 vbe module | |
4 | * Copyright 2000 Egbert Eich | |
5 | * | |
6 | * The mode query/save/set/restore functions from the vesa driver | |
7 | * have been moved here. | |
8 | * Copyright (c) 2000 by Conectiva S.A. (http://www.conectiva.com) | |
9 | * Authors: Paulo César Pereira de Andrade <pcpa@conectiva.com.br> | |
10 | */ | |
11 | ||
12 | #ifdef HAVE_XORG_CONFIG_H | |
13 | #include <xorg-config.h> | |
14 | #endif | |
15 | ||
16 | #include <string.h> | |
17 | ||
18 | #include "xf86.h" | |
19 | #include "xf86Modes.h" | |
20 | #include "vbe.h" | |
21 | #include <X11/extensions/dpmsconst.h> | |
22 | ||
23 | #define VERSION(x) VBE_VERSION_MAJOR(x),VBE_VERSION_MINOR(x) | |
24 | ||
25 | #if X_BYTE_ORDER == X_LITTLE_ENDIAN | |
26 | #define B_O16(x) (x) | |
27 | #define B_O32(x) (x) | |
28 | #else | |
29 | #define B_O16(x) ((((x) & 0xff) << 8) | (((x) & 0xff) >> 8)) | |
30 | #define B_O32(x) ((((x) & 0xff) << 24) | (((x) & 0xff00) << 8) \ | |
31 | | (((x) & 0xff0000) >> 8) | (((x) & 0xff000000) >> 24)) | |
32 | #endif | |
33 | #define L_ADD(x) (B_O32(x) & 0xffff) + ((B_O32(x) >> 12) & 0xffff00) | |
34 | ||
35 | #define FARP(p) (((unsigned)(p & 0xffff0000) >> 12) | (p & 0xffff)) | |
36 | #define R16(v) ((v) & 0xffff) | |
37 | ||
38 | static unsigned char *vbeReadEDID(vbeInfoPtr pVbe); | |
39 | static Bool vbeProbeDDC(vbeInfoPtr pVbe); | |
40 | ||
41 | static const char vbeVersionString[] = "VBE2"; | |
42 | ||
43 | vbeInfoPtr | |
44 | VBEInit(xf86Int10InfoPtr pInt, int entityIndex) | |
45 | { | |
46 | return VBEExtendedInit(pInt, entityIndex, 0); | |
47 | } | |
48 | ||
49 | vbeInfoPtr | |
50 | VBEExtendedInit(xf86Int10InfoPtr pInt, int entityIndex, int Flags) | |
51 | { | |
52 | int RealOff; | |
53 | pointer page = NULL; | |
54 | ScrnInfoPtr pScrn = xf86FindScreenForEntity(entityIndex); | |
55 | vbeControllerInfoPtr vbe = NULL; | |
56 | Bool init_int10 = FALSE; | |
57 | vbeInfoPtr vip = NULL; | |
58 | int screen; | |
59 | ||
60 | if (!pScrn) | |
61 | return NULL; | |
62 | screen = pScrn->scrnIndex; | |
63 | ||
64 | if (!pInt) { | |
65 | if (!xf86LoadSubModule(pScrn, "int10")) | |
66 | goto error; | |
67 | ||
68 | xf86DrvMsg(screen, X_INFO, "initializing int10\n"); | |
69 | pInt = xf86ExtendedInitInt10(entityIndex, Flags); | |
70 | if (!pInt) | |
71 | goto error; | |
72 | init_int10 = TRUE; | |
73 | } | |
74 | ||
75 | page = xf86Int10AllocPages(pInt, 1, &RealOff); | |
76 | if (!page) | |
77 | goto error; | |
78 | vbe = (vbeControllerInfoPtr) page; | |
79 | memcpy(vbe->VbeSignature, vbeVersionString, 4); | |
80 | ||
81 | pInt->ax = 0x4F00; | |
82 | pInt->es = SEG_ADDR(RealOff); | |
83 | pInt->di = SEG_OFF(RealOff); | |
84 | pInt->num = 0x10; | |
85 | ||
86 | xf86ExecX86int10(pInt); | |
87 | ||
88 | if ((pInt->ax & 0xff) != 0x4f) { | |
89 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA BIOS not detected\n"); | |
90 | goto error; | |
91 | } | |
92 | ||
93 | switch (pInt->ax & 0xff00) { | |
94 | case 0: | |
95 | xf86DrvMsg(screen, X_INFO, "VESA BIOS detected\n"); | |
96 | break; | |
97 | case 0x100: | |
98 | xf86DrvMsg(screen, X_INFO, "VESA BIOS function failed\n"); | |
99 | goto error; | |
100 | case 0x200: | |
101 | xf86DrvMsg(screen, X_INFO, "VESA BIOS not supported\n"); | |
102 | goto error; | |
103 | case 0x300: | |
104 | xf86DrvMsg(screen, X_INFO, "VESA BIOS not supported in current mode\n"); | |
105 | goto error; | |
106 | default: | |
107 | xf86DrvMsg(screen, X_INFO, "Invalid\n"); | |
108 | goto error; | |
109 | } | |
110 | ||
111 | xf86DrvMsgVerb(screen, X_INFO, 4, | |
112 | "VbeVersion is %d, OemStringPtr is 0x%08lx,\n" | |
113 | "\tOemVendorNamePtr is 0x%08lx, OemProductNamePtr is 0x%08lx,\n" | |
114 | "\tOemProductRevPtr is 0x%08lx\n", | |
115 | vbe->VbeVersion, (unsigned long) vbe->OemStringPtr, | |
116 | (unsigned long) vbe->OemVendorNamePtr, | |
117 | (unsigned long) vbe->OemProductNamePtr, | |
118 | (unsigned long) vbe->OemProductRevPtr); | |
119 | ||
120 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE Version %i.%i\n", | |
121 | VERSION(vbe->VbeVersion)); | |
122 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE Total Mem: %i kB\n", | |
123 | vbe->TotalMem * 64); | |
124 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE OEM: %s\n", | |
125 | (CARD8 *) xf86int10Addr(pInt, L_ADD(vbe->OemStringPtr))); | |
126 | ||
127 | if (B_O16(vbe->VbeVersion) >= 0x200) { | |
128 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE OEM Software Rev: %i.%i\n", | |
129 | VERSION(vbe->OemSoftwareRev)); | |
130 | if (vbe->OemVendorNamePtr) | |
131 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE OEM Vendor: %s\n", | |
132 | (CARD8 *) xf86int10Addr(pInt, | |
133 | L_ADD(vbe-> | |
134 | OemVendorNamePtr))); | |
135 | if (vbe->OemProductNamePtr) | |
136 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE OEM Product: %s\n", | |
137 | (CARD8 *) xf86int10Addr(pInt, | |
138 | L_ADD(vbe-> | |
139 | OemProductNamePtr))); | |
140 | if (vbe->OemProductRevPtr) | |
141 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE OEM Product Rev: %s\n", | |
142 | (CARD8 *) xf86int10Addr(pInt, | |
143 | L_ADD(vbe-> | |
144 | OemProductRevPtr))); | |
145 | } | |
146 | vip = (vbeInfoPtr) xnfalloc(sizeof(vbeInfoRec)); | |
147 | vip->version = B_O16(vbe->VbeVersion); | |
148 | vip->pInt10 = pInt; | |
149 | vip->ddc = DDC_UNCHECKED; | |
150 | vip->memory = page; | |
151 | vip->real_mode_base = RealOff; | |
152 | vip->num_pages = 1; | |
153 | vip->init_int10 = init_int10; | |
154 | ||
155 | return vip; | |
156 | ||
157 | error: | |
158 | if (page) | |
159 | xf86Int10FreePages(pInt, page, 1); | |
160 | if (init_int10) | |
161 | xf86FreeInt10(pInt); | |
162 | return NULL; | |
163 | } | |
164 | ||
165 | void | |
166 | vbeFree(vbeInfoPtr pVbe) | |
167 | { | |
168 | if (!pVbe) | |
169 | return; | |
170 | ||
171 | xf86Int10FreePages(pVbe->pInt10, pVbe->memory, pVbe->num_pages); | |
172 | /* If we have initalized int10 we ought to free it, too */ | |
173 | if (pVbe->init_int10) | |
174 | xf86FreeInt10(pVbe->pInt10); | |
175 | free(pVbe); | |
176 | return; | |
177 | } | |
178 | ||
179 | static Bool | |
180 | vbeProbeDDC(vbeInfoPtr pVbe) | |
181 | { | |
182 | const char *ddc_level; | |
183 | int screen = pVbe->pInt10->pScrn->scrnIndex; | |
184 | ||
185 | if (pVbe->ddc == DDC_NONE) | |
186 | return FALSE; | |
187 | if (pVbe->ddc != DDC_UNCHECKED) | |
188 | return TRUE; | |
189 | ||
190 | pVbe->pInt10->ax = 0x4F15; | |
191 | pVbe->pInt10->bx = 0; | |
192 | pVbe->pInt10->cx = 0; | |
193 | pVbe->pInt10->es = 0; | |
194 | pVbe->pInt10->di = 0; | |
195 | pVbe->pInt10->num = 0x10; | |
196 | ||
197 | xf86ExecX86int10(pVbe->pInt10); | |
198 | ||
199 | if ((pVbe->pInt10->ax & 0xff) != 0x4f) { | |
200 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE DDC not supported\n"); | |
201 | pVbe->ddc = DDC_NONE; | |
202 | return FALSE; | |
203 | } | |
204 | ||
205 | switch ((pVbe->pInt10->ax >> 8) & 0xff) { | |
206 | case 0: | |
207 | xf86DrvMsg(screen, X_INFO, "VESA VBE DDC supported\n"); | |
208 | switch (pVbe->pInt10->bx & 0x3) { | |
209 | case 0: | |
210 | ddc_level = " none"; | |
211 | pVbe->ddc = DDC_NONE; | |
212 | break; | |
213 | case 1: | |
214 | ddc_level = " 1"; | |
215 | pVbe->ddc = DDC_1; | |
216 | break; | |
217 | case 2: | |
218 | ddc_level = " 2"; | |
219 | pVbe->ddc = DDC_2; | |
220 | break; | |
221 | case 3: | |
222 | ddc_level = " 1 + 2"; | |
223 | pVbe->ddc = DDC_1_2; | |
224 | break; | |
225 | default: | |
226 | ddc_level = ""; | |
227 | pVbe->ddc = DDC_NONE; | |
228 | break; | |
229 | } | |
230 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE DDC Level%s\n", ddc_level); | |
231 | if (pVbe->pInt10->bx & 0x4) { | |
232 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE DDC Screen blanked" | |
233 | "for data transfer\n"); | |
234 | pVbe->ddc_blank = TRUE; | |
235 | } | |
236 | else | |
237 | pVbe->ddc_blank = FALSE; | |
238 | ||
239 | xf86DrvMsgVerb(screen, X_INFO, 3, | |
240 | "VESA VBE DDC transfer in appr. %x sec.\n", | |
241 | (pVbe->pInt10->bx >> 8) & 0xff); | |
242 | } | |
243 | ||
244 | return TRUE; | |
245 | } | |
246 | ||
247 | typedef enum { | |
248 | VBEOPT_NOVBE, | |
249 | VBEOPT_NODDC | |
250 | } VBEOpts; | |
251 | ||
252 | static const OptionInfoRec VBEOptions[] = { | |
253 | {VBEOPT_NOVBE, "NoVBE", OPTV_BOOLEAN, {0}, FALSE}, | |
254 | {VBEOPT_NODDC, "NoDDC", OPTV_BOOLEAN, {0}, FALSE}, | |
255 | {-1, NULL, OPTV_NONE, {0}, FALSE}, | |
256 | }; | |
257 | ||
258 | static unsigned char * | |
259 | vbeReadEDID(vbeInfoPtr pVbe) | |
260 | { | |
261 | int RealOff = pVbe->real_mode_base; | |
262 | pointer page = pVbe->memory; | |
263 | unsigned char *tmp = NULL; | |
264 | Bool novbe = FALSE; | |
265 | Bool noddc = FALSE; | |
266 | ScrnInfoPtr pScrn = pVbe->pInt10->pScrn; | |
267 | int screen = pScrn->scrnIndex; | |
268 | OptionInfoPtr options; | |
269 | ||
270 | if (!page) | |
271 | return NULL; | |
272 | ||
273 | options = xnfalloc(sizeof(VBEOptions)); | |
274 | (void) memcpy(options, VBEOptions, sizeof(VBEOptions)); | |
275 | xf86ProcessOptions(screen, pScrn->options, options); | |
276 | xf86GetOptValBool(options, VBEOPT_NOVBE, &novbe); | |
277 | xf86GetOptValBool(options, VBEOPT_NODDC, &noddc); | |
278 | free(options); | |
279 | if (novbe || noddc) | |
280 | return NULL; | |
281 | ||
282 | if (!vbeProbeDDC(pVbe)) | |
283 | goto error; | |
284 | ||
285 | memset(page, 0, sizeof(vbeInfoPtr)); | |
286 | strcpy(page, vbeVersionString); | |
287 | ||
288 | pVbe->pInt10->ax = 0x4F15; | |
289 | pVbe->pInt10->bx = 0x01; | |
290 | pVbe->pInt10->cx = 0; | |
291 | pVbe->pInt10->dx = 0; | |
292 | pVbe->pInt10->es = SEG_ADDR(RealOff); | |
293 | pVbe->pInt10->di = SEG_OFF(RealOff); | |
294 | pVbe->pInt10->num = 0x10; | |
295 | ||
296 | xf86ExecX86int10(pVbe->pInt10); | |
297 | ||
298 | if ((pVbe->pInt10->ax & 0xff) != 0x4f) { | |
299 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE DDC invalid\n"); | |
300 | goto error; | |
301 | } | |
302 | switch (pVbe->pInt10->ax & 0xff00) { | |
303 | case 0x0: | |
304 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE DDC read successfully\n"); | |
305 | tmp = (unsigned char *) xnfalloc(128); | |
306 | memcpy(tmp, page, 128); | |
307 | break; | |
308 | case 0x100: | |
309 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE DDC read failed\n"); | |
310 | break; | |
311 | default: | |
312 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE DDC unkown failure %i\n", | |
313 | pVbe->pInt10->ax & 0xff00); | |
314 | break; | |
315 | } | |
316 | ||
317 | error: | |
318 | return tmp; | |
319 | } | |
320 | ||
321 | xf86MonPtr | |
322 | vbeDoEDID(vbeInfoPtr pVbe, pointer unused) | |
323 | { | |
324 | unsigned char *DDC_data = NULL; | |
325 | ||
326 | if (!pVbe) | |
327 | return NULL; | |
328 | if (pVbe->version < 0x200) | |
329 | return NULL; | |
330 | ||
331 | DDC_data = vbeReadEDID(pVbe); | |
332 | ||
333 | if (!DDC_data) | |
334 | return NULL; | |
335 | ||
336 | return xf86InterpretEDID(pVbe->pInt10->pScrn->scrnIndex, DDC_data); | |
337 | } | |
338 | ||
339 | #define GET_UNALIGNED2(x) \ | |
340 | ((*(CARD16*)(x)) | (*(((CARD16*)(x) + 1))) << 16) | |
341 | ||
342 | VbeInfoBlock * | |
343 | VBEGetVBEInfo(vbeInfoPtr pVbe) | |
344 | { | |
345 | VbeInfoBlock *block = NULL; | |
346 | int i, pStr, pModes; | |
347 | char *str; | |
348 | CARD16 major, *modes; | |
349 | ||
350 | memset(pVbe->memory, 0, sizeof(VbeInfoBlock)); | |
351 | ||
352 | /* | |
353 | Input: | |
354 | AH := 4Fh Super VGA support | |
355 | AL := 00h Return Super VGA information | |
356 | ES:DI := Pointer to buffer | |
357 | ||
358 | Output: | |
359 | AX := status | |
360 | (All other registers are preserved) | |
361 | */ | |
362 | ||
363 | ((char *) pVbe->memory)[0] = 'V'; | |
364 | ((char *) pVbe->memory)[1] = 'B'; | |
365 | ((char *) pVbe->memory)[2] = 'E'; | |
366 | ((char *) pVbe->memory)[3] = '2'; | |
367 | ||
368 | pVbe->pInt10->num = 0x10; | |
369 | pVbe->pInt10->ax = 0x4f00; | |
370 | pVbe->pInt10->es = SEG_ADDR(pVbe->real_mode_base); | |
371 | pVbe->pInt10->di = SEG_OFF(pVbe->real_mode_base); | |
372 | xf86ExecX86int10(pVbe->pInt10); | |
373 | ||
374 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
375 | return NULL; | |
376 | ||
377 | block = calloc(sizeof(VbeInfoBlock), 1); | |
378 | block->VESASignature[0] = ((char *) pVbe->memory)[0]; | |
379 | block->VESASignature[1] = ((char *) pVbe->memory)[1]; | |
380 | block->VESASignature[2] = ((char *) pVbe->memory)[2]; | |
381 | block->VESASignature[3] = ((char *) pVbe->memory)[3]; | |
382 | ||
383 | block->VESAVersion = *(CARD16 *) (((char *) pVbe->memory) + 4); | |
384 | major = (unsigned) block->VESAVersion >> 8; | |
385 | ||
386 | pStr = GET_UNALIGNED2((((char *) pVbe->memory) + 6)); | |
387 | str = xf86int10Addr(pVbe->pInt10, FARP(pStr)); | |
388 | block->OEMStringPtr = strdup(str); | |
389 | ||
390 | block->Capabilities[0] = ((char *) pVbe->memory)[10]; | |
391 | block->Capabilities[1] = ((char *) pVbe->memory)[11]; | |
392 | block->Capabilities[2] = ((char *) pVbe->memory)[12]; | |
393 | block->Capabilities[3] = ((char *) pVbe->memory)[13]; | |
394 | ||
395 | pModes = GET_UNALIGNED2((((char *) pVbe->memory) + 14)); | |
396 | modes = xf86int10Addr(pVbe->pInt10, FARP(pModes)); | |
397 | i = 0; | |
398 | while (modes[i] != 0xffff) | |
399 | i++; | |
400 | block->VideoModePtr = malloc(sizeof(CARD16) * (i + 1)); | |
401 | memcpy(block->VideoModePtr, modes, sizeof(CARD16) * i); | |
402 | block->VideoModePtr[i] = 0xffff; | |
403 | ||
404 | block->TotalMemory = *(CARD16 *) (((char *) pVbe->memory) + 18); | |
405 | ||
406 | if (major < 2) | |
407 | memcpy(&block->OemSoftwareRev, ((char *) pVbe->memory) + 20, 236); | |
408 | else { | |
409 | block->OemSoftwareRev = *(CARD16 *) (((char *) pVbe->memory) + 20); | |
410 | pStr = GET_UNALIGNED2((((char *) pVbe->memory) + 22)); | |
411 | str = xf86int10Addr(pVbe->pInt10, FARP(pStr)); | |
412 | block->OemVendorNamePtr = strdup(str); | |
413 | pStr = GET_UNALIGNED2((((char *) pVbe->memory) + 26)); | |
414 | str = xf86int10Addr(pVbe->pInt10, FARP(pStr)); | |
415 | block->OemProductNamePtr = strdup(str); | |
416 | pStr = GET_UNALIGNED2((((char *) pVbe->memory) + 30)); | |
417 | str = xf86int10Addr(pVbe->pInt10, FARP(pStr)); | |
418 | block->OemProductRevPtr = strdup(str); | |
419 | memcpy(&block->Reserved, ((char *) pVbe->memory) + 34, 222); | |
420 | memcpy(&block->OemData, ((char *) pVbe->memory) + 256, 256); | |
421 | } | |
422 | ||
423 | return block; | |
424 | } | |
425 | ||
426 | void | |
427 | VBEFreeVBEInfo(VbeInfoBlock * block) | |
428 | { | |
429 | free(block->OEMStringPtr); | |
430 | free(block->VideoModePtr); | |
431 | if (((unsigned) block->VESAVersion >> 8) >= 2) { | |
432 | free(block->OemVendorNamePtr); | |
433 | free(block->OemProductNamePtr); | |
434 | free(block->OemProductRevPtr); | |
435 | } | |
436 | free(block); | |
437 | } | |
438 | ||
439 | Bool | |
440 | VBESetVBEMode(vbeInfoPtr pVbe, int mode, VbeCRTCInfoBlock * block) | |
441 | { | |
442 | /* | |
443 | Input: | |
444 | AH := 4Fh Super VGA support | |
445 | AL := 02h Set Super VGA video mode | |
446 | BX := Video mode | |
447 | D0-D8 := Mode number | |
448 | D9-D10 := Reserved (must be 0) | |
449 | D11 := 0 Use current default refresh rate | |
450 | := 1 Use user specified CRTC values for refresh rate | |
451 | D12-13 Reserved for VBE/AF (must be 0) | |
452 | D14 := 0 Use windowed frame buffer model | |
453 | := 1 Use linear/flat frame buffer model | |
454 | D15 := 0 Clear video memory | |
455 | := 1 Don't clear video memory | |
456 | ES:DI := Pointer to VbeCRTCInfoBlock structure | |
457 | ||
458 | Output: AX = Status | |
459 | (All other registers are preserved) | |
460 | */ | |
461 | pVbe->pInt10->num = 0x10; | |
462 | pVbe->pInt10->ax = 0x4f02; | |
463 | pVbe->pInt10->bx = mode; | |
464 | if (block) { | |
465 | pVbe->pInt10->bx |= 1 << 11; | |
466 | memcpy(pVbe->memory, block, sizeof(VbeCRTCInfoBlock)); | |
467 | pVbe->pInt10->es = SEG_ADDR(pVbe->real_mode_base); | |
468 | pVbe->pInt10->di = SEG_OFF(pVbe->real_mode_base); | |
469 | } | |
470 | else | |
471 | pVbe->pInt10->bx &= ~(1 << 11); | |
472 | ||
473 | xf86ExecX86int10(pVbe->pInt10); | |
474 | ||
475 | return (R16(pVbe->pInt10->ax) == 0x4f); | |
476 | } | |
477 | ||
478 | Bool | |
479 | VBEGetVBEMode(vbeInfoPtr pVbe, int *mode) | |
480 | { | |
481 | /* | |
482 | Input: | |
483 | AH := 4Fh Super VGA support | |
484 | AL := 03h Return current video mode | |
485 | ||
486 | Output: | |
487 | AX := Status | |
488 | BX := Current video mode | |
489 | (All other registers are preserved) | |
490 | */ | |
491 | pVbe->pInt10->num = 0x10; | |
492 | pVbe->pInt10->ax = 0x4f03; | |
493 | ||
494 | xf86ExecX86int10(pVbe->pInt10); | |
495 | ||
496 | if (R16(pVbe->pInt10->ax) == 0x4f) { | |
497 | *mode = R16(pVbe->pInt10->bx); | |
498 | ||
499 | return TRUE; | |
500 | } | |
501 | ||
502 | return FALSE; | |
503 | } | |
504 | ||
505 | VbeModeInfoBlock * | |
506 | VBEGetModeInfo(vbeInfoPtr pVbe, int mode) | |
507 | { | |
508 | VbeModeInfoBlock *block = NULL; | |
509 | ||
510 | memset(pVbe->memory, 0, sizeof(VbeModeInfoBlock)); | |
511 | ||
512 | /* | |
513 | Input: | |
514 | AH := 4Fh Super VGA support | |
515 | AL := 01h Return Super VGA mode information | |
516 | CX := Super VGA video mode | |
517 | (mode number must be one of those returned by Function 0) | |
518 | ES:DI := Pointer to buffer | |
519 | ||
520 | Output: | |
521 | AX := status | |
522 | (All other registers are preserved) | |
523 | */ | |
524 | pVbe->pInt10->num = 0x10; | |
525 | pVbe->pInt10->ax = 0x4f01; | |
526 | pVbe->pInt10->cx = mode; | |
527 | pVbe->pInt10->es = SEG_ADDR(pVbe->real_mode_base); | |
528 | pVbe->pInt10->di = SEG_OFF(pVbe->real_mode_base); | |
529 | xf86ExecX86int10(pVbe->pInt10); | |
530 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
531 | return NULL; | |
532 | ||
533 | block = malloc(sizeof(VbeModeInfoBlock)); | |
534 | if (block) | |
535 | memcpy(block, pVbe->memory, sizeof(*block)); | |
536 | ||
537 | return block; | |
538 | } | |
539 | ||
540 | void | |
541 | VBEFreeModeInfo(VbeModeInfoBlock * block) | |
542 | { | |
543 | free(block); | |
544 | } | |
545 | ||
546 | Bool | |
547 | VBESaveRestore(vbeInfoPtr pVbe, vbeSaveRestoreFunction function, | |
548 | pointer *memory, int *size, int *real_mode_pages) | |
549 | { | |
550 | /* | |
551 | Input: | |
552 | AH := 4Fh Super VGA support | |
553 | AL := 04h Save/restore Super VGA video state | |
554 | DL := 00h Return save/restore state buffer size | |
555 | CX := Requested states | |
556 | D0 = Save/restore video hardware state | |
557 | D1 = Save/restore video BIOS data state | |
558 | D2 = Save/restore video DAC state | |
559 | D3 = Save/restore Super VGA state | |
560 | ||
561 | Output: | |
562 | AX = Status | |
563 | BX = Number of 64-byte blocks to hold the state buffer | |
564 | (All other registers are preserved) | |
565 | ||
566 | Input: | |
567 | AH := 4Fh Super VGA support | |
568 | AL := 04h Save/restore Super VGA video state | |
569 | DL := 01h Save Super VGA video state | |
570 | CX := Requested states (see above) | |
571 | ES:BX := Pointer to buffer | |
572 | ||
573 | Output: | |
574 | AX := Status | |
575 | (All other registers are preserved) | |
576 | ||
577 | Input: | |
578 | AH := 4Fh Super VGA support | |
579 | AL := 04h Save/restore Super VGA video state | |
580 | DL := 02h Restore Super VGA video state | |
581 | CX := Requested states (see above) | |
582 | ES:BX := Pointer to buffer | |
583 | ||
584 | Output: | |
585 | AX := Status | |
586 | (All other registers are preserved) | |
587 | */ | |
588 | ||
589 | if ((pVbe->version & 0xff00) > 0x100) { | |
590 | int screen = pVbe->pInt10->pScrn->scrnIndex; | |
591 | ||
592 | if (function == MODE_QUERY || (function == MODE_SAVE && !*memory)) { | |
593 | /* Query amount of memory to save state */ | |
594 | ||
595 | pVbe->pInt10->num = 0x10; | |
596 | pVbe->pInt10->ax = 0x4f04; | |
597 | pVbe->pInt10->dx = 0; | |
598 | pVbe->pInt10->cx = 0x000f; | |
599 | xf86ExecX86int10(pVbe->pInt10); | |
600 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
601 | return FALSE; | |
602 | ||
603 | if (function == MODE_SAVE) { | |
604 | int npages = (R16(pVbe->pInt10->bx) * 64) / 4096 + 1; | |
605 | ||
606 | if ((*memory = xf86Int10AllocPages(pVbe->pInt10, npages, | |
607 | real_mode_pages)) == NULL) { | |
608 | xf86DrvMsg(screen, X_ERROR, | |
609 | "Cannot allocate memory to save SVGA state.\n"); | |
610 | return FALSE; | |
611 | } | |
612 | } | |
613 | *size = pVbe->pInt10->bx * 64; | |
614 | } | |
615 | ||
616 | /* Save/Restore Super VGA state */ | |
617 | if (function != MODE_QUERY) { | |
618 | ||
619 | if (!*memory) | |
620 | return FALSE; | |
621 | pVbe->pInt10->num = 0x10; | |
622 | pVbe->pInt10->ax = 0x4f04; | |
623 | switch (function) { | |
624 | case MODE_SAVE: | |
625 | pVbe->pInt10->dx = 1; | |
626 | break; | |
627 | case MODE_RESTORE: | |
628 | pVbe->pInt10->dx = 2; | |
629 | break; | |
630 | case MODE_QUERY: | |
631 | return FALSE; | |
632 | } | |
633 | pVbe->pInt10->cx = 0x000f; | |
634 | ||
635 | pVbe->pInt10->es = SEG_ADDR(*real_mode_pages); | |
636 | pVbe->pInt10->bx = SEG_OFF(*real_mode_pages); | |
637 | xf86ExecX86int10(pVbe->pInt10); | |
638 | return (R16(pVbe->pInt10->ax) == 0x4f); | |
639 | ||
640 | } | |
641 | } | |
642 | return TRUE; | |
643 | } | |
644 | ||
645 | Bool | |
646 | VBEBankSwitch(vbeInfoPtr pVbe, unsigned int iBank, int window) | |
647 | { | |
648 | /* | |
649 | Input: | |
650 | AH := 4Fh Super VGA support | |
651 | AL := 05h | |
652 | ||
653 | Output: | |
654 | */ | |
655 | pVbe->pInt10->num = 0x10; | |
656 | pVbe->pInt10->ax = 0x4f05; | |
657 | pVbe->pInt10->bx = window; | |
658 | pVbe->pInt10->dx = iBank; | |
659 | xf86ExecX86int10(pVbe->pInt10); | |
660 | ||
661 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
662 | return FALSE; | |
663 | ||
664 | return TRUE; | |
665 | } | |
666 | ||
667 | Bool | |
668 | VBESetGetLogicalScanlineLength(vbeInfoPtr pVbe, vbeScanwidthCommand command, | |
669 | int width, int *pixels, int *bytes, int *max) | |
670 | { | |
671 | if (command < SCANWID_SET || command > SCANWID_GET_MAX) | |
672 | return FALSE; | |
673 | ||
674 | /* | |
675 | Input: | |
676 | AX := 4F06h VBE Set/Get Logical Scan Line Length | |
677 | BL := 00h Set Scan Line Length in Pixels | |
678 | := 01h Get Scan Line Length | |
679 | := 02h Set Scan Line Length in Bytes | |
680 | := 03h Get Maximum Scan Line Length | |
681 | CX := If BL=00h Desired Width in Pixels | |
682 | If BL=02h Desired Width in Bytes | |
683 | (Ignored for Get Functions) | |
684 | ||
685 | Output: | |
686 | AX := VBE Return Status | |
687 | BX := Bytes Per Scan Line | |
688 | CX := Actual Pixels Per Scan Line | |
689 | (truncated to nearest complete pixel) | |
690 | DX := Maximum Number of Scan Lines | |
691 | */ | |
692 | ||
693 | pVbe->pInt10->num = 0x10; | |
694 | pVbe->pInt10->ax = 0x4f06; | |
695 | pVbe->pInt10->bx = command; | |
696 | if (command == SCANWID_SET || command == SCANWID_SET_BYTES) | |
697 | pVbe->pInt10->cx = width; | |
698 | xf86ExecX86int10(pVbe->pInt10); | |
699 | ||
700 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
701 | return FALSE; | |
702 | ||
703 | if (command == SCANWID_GET || command == SCANWID_GET_MAX) { | |
704 | if (pixels) | |
705 | *pixels = R16(pVbe->pInt10->cx); | |
706 | if (bytes) | |
707 | *bytes = R16(pVbe->pInt10->bx); | |
708 | if (max) | |
709 | *max = R16(pVbe->pInt10->dx); | |
710 | } | |
711 | ||
712 | return TRUE; | |
713 | } | |
714 | ||
715 | Bool | |
716 | VBESetDisplayStart(vbeInfoPtr pVbe, int x, int y, Bool wait_retrace) | |
717 | { | |
718 | pVbe->pInt10->num = 0x10; | |
719 | pVbe->pInt10->ax = 0x4f07; | |
720 | pVbe->pInt10->bx = wait_retrace ? 0x80 : 0x00; | |
721 | pVbe->pInt10->cx = x; | |
722 | pVbe->pInt10->dx = y; | |
723 | xf86ExecX86int10(pVbe->pInt10); | |
724 | ||
725 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
726 | return FALSE; | |
727 | ||
728 | return TRUE; | |
729 | } | |
730 | ||
731 | Bool | |
732 | VBEGetDisplayStart(vbeInfoPtr pVbe, int *x, int *y) | |
733 | { | |
734 | pVbe->pInt10->num = 0x10; | |
735 | pVbe->pInt10->ax = 0x4f07; | |
736 | pVbe->pInt10->bx = 0x01; | |
737 | xf86ExecX86int10(pVbe->pInt10); | |
738 | ||
739 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
740 | return FALSE; | |
741 | ||
742 | *x = pVbe->pInt10->cx; | |
743 | *y = pVbe->pInt10->dx; | |
744 | ||
745 | return TRUE; | |
746 | } | |
747 | ||
748 | int | |
749 | VBESetGetDACPaletteFormat(vbeInfoPtr pVbe, int bits) | |
750 | { | |
751 | /* | |
752 | Input: | |
753 | AX := 4F08h VBE Set/Get Palette Format | |
754 | BL := 00h Set DAC Palette Format | |
755 | := 01h Get DAC Palette Format | |
756 | BH := Desired bits of color per primary | |
757 | (Set DAC Palette Format only) | |
758 | ||
759 | Output: | |
760 | AX := VBE Return Status | |
761 | BH := Current number of bits of color per primary | |
762 | */ | |
763 | ||
764 | pVbe->pInt10->num = 0x10; | |
765 | pVbe->pInt10->ax = 0x4f08; | |
766 | if (!bits) | |
767 | pVbe->pInt10->bx = 0x01; | |
768 | else | |
769 | pVbe->pInt10->bx = (bits & 0x00ff) << 8; | |
770 | xf86ExecX86int10(pVbe->pInt10); | |
771 | ||
772 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
773 | return 0; | |
774 | ||
775 | return (bits != 0 ? bits : (pVbe->pInt10->bx >> 8) & 0x00ff); | |
776 | } | |
777 | ||
778 | CARD32 * | |
779 | VBESetGetPaletteData(vbeInfoPtr pVbe, Bool set, int first, int num, | |
780 | CARD32 *data, Bool secondary, Bool wait_retrace) | |
781 | { | |
782 | /* | |
783 | Input: | |
784 | (16-bit) | |
785 | AX := 4F09h VBE Load/Unload Palette Data | |
786 | BL := 00h Set Palette Data | |
787 | := 01h Get Palette Data | |
788 | := 02h Set Secondary Palette Data | |
789 | := 03h Get Secondary Palette Data | |
790 | := 80h Set Palette Data during Vertical Retrace | |
791 | CX := Number of palette registers to update (to a maximum of 256) | |
792 | DX := First of the palette registers to update (start) | |
793 | ES:DI := Table of palette values (see below for format) | |
794 | ||
795 | Output: | |
796 | AX := VBE Return Status | |
797 | ||
798 | Input: | |
799 | (32-bit) | |
800 | BL := 00h Set Palette Data | |
801 | := 80h Set Palette Data during Vertical Retrace | |
802 | CX := Number of palette registers to update (to a maximum of 256) | |
803 | DX := First of the palette registers to update (start) | |
804 | ES:EDI := Table of palette values (see below for format) | |
805 | DS := Selector for memory mapped registers | |
806 | */ | |
807 | ||
808 | pVbe->pInt10->num = 0x10; | |
809 | pVbe->pInt10->ax = 0x4f09; | |
810 | if (!secondary) | |
811 | pVbe->pInt10->bx = set && wait_retrace ? 0x80 : set ? 0 : 1; | |
812 | else | |
813 | pVbe->pInt10->bx = set ? 2 : 3; | |
814 | pVbe->pInt10->cx = num; | |
815 | pVbe->pInt10->dx = first; | |
816 | pVbe->pInt10->es = SEG_ADDR(pVbe->real_mode_base); | |
817 | pVbe->pInt10->di = SEG_OFF(pVbe->real_mode_base); | |
818 | if (set) | |
819 | memcpy(pVbe->memory, data, num * sizeof(CARD32)); | |
820 | xf86ExecX86int10(pVbe->pInt10); | |
821 | ||
822 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
823 | return NULL; | |
824 | ||
825 | if (set) | |
826 | return data; | |
827 | ||
828 | data = malloc(num * sizeof(CARD32)); | |
829 | memcpy(data, pVbe->memory, num * sizeof(CARD32)); | |
830 | ||
831 | return data; | |
832 | } | |
833 | ||
834 | VBEpmi * | |
835 | VBEGetVBEpmi(vbeInfoPtr pVbe) | |
836 | { | |
837 | VBEpmi *pmi; | |
838 | ||
839 | /* | |
840 | Input: | |
841 | AH := 4Fh Super VGA support | |
842 | AL := 0Ah Protected Mode Interface | |
843 | BL := 00h Return Protected Mode Table | |
844 | ||
845 | Output: | |
846 | AX := Status | |
847 | ES := Real Mode Segment of Table | |
848 | DI := Offset of Table | |
849 | CX := Lenght of Table including protected mode code in bytes (for copying purposes) | |
850 | (All other registers are preserved) | |
851 | */ | |
852 | ||
853 | pVbe->pInt10->num = 0x10; | |
854 | pVbe->pInt10->ax = 0x4f0a; | |
855 | pVbe->pInt10->bx = 0; | |
856 | pVbe->pInt10->di = 0; | |
857 | xf86ExecX86int10(pVbe->pInt10); | |
858 | ||
859 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
860 | return NULL; | |
861 | ||
862 | pmi = malloc(sizeof(VBEpmi)); | |
863 | pmi->seg_tbl = R16(pVbe->pInt10->es); | |
864 | pmi->tbl_off = R16(pVbe->pInt10->di); | |
865 | pmi->tbl_len = R16(pVbe->pInt10->cx); | |
866 | ||
867 | return pmi; | |
868 | } | |
869 | ||
870 | #if 0 | |
871 | vbeModeInfoPtr | |
872 | VBEBuildVbeModeList(vbeInfoPtr pVbe, VbeInfoBlock * vbe) | |
873 | { | |
874 | vbeModeInfoPtr ModeList = NULL; | |
875 | ||
876 | int i = 0; | |
877 | ||
878 | while (vbe->VideoModePtr[i] != 0xffff) { | |
879 | vbeModeInfoPtr m; | |
880 | VbeModeInfoBlock *mode; | |
881 | int id = vbe->VideoModePtr[i++]; | |
882 | int bpp; | |
883 | ||
884 | if ((mode = VBEGetModeInfo(pVbe, id)) == NULL) | |
885 | continue; | |
886 | ||
887 | bpp = mode->BitsPerPixel; | |
888 | ||
889 | m = xnfcalloc(sizeof(vbeModeInfoRec), 1); | |
890 | m->width = mode->XResolution; | |
891 | m->height = mode->YResolution; | |
892 | m->bpp = bpp; | |
893 | m->n = id; | |
894 | m->next = ModeList; | |
895 | ||
896 | xf86DrvMsgVerb(pVbe->pInt10->pScrn->scrnIndex, X_PROBED, 3, | |
897 | "BIOS reported VESA mode 0x%x: x:%i y:%i bpp:%i\n", | |
898 | m->n, m->width, m->height, m->bpp); | |
899 | ||
900 | ModeList = m; | |
901 | ||
902 | VBEFreeModeInfo(mode); | |
903 | } | |
904 | return ModeList; | |
905 | } | |
906 | ||
907 | unsigned short | |
908 | VBECalcVbeModeIndex(vbeModeInfoPtr m, DisplayModePtr mode, int bpp) | |
909 | { | |
910 | while (m) { | |
911 | if (bpp == m->bpp | |
912 | && mode->HDisplay == m->width && mode->VDisplay == m->height) | |
913 | return m->n; | |
914 | m = m->next; | |
915 | } | |
916 | return 0; | |
917 | } | |
918 | #endif | |
919 | ||
920 | void | |
921 | VBEVesaSaveRestore(vbeInfoPtr pVbe, vbeSaveRestorePtr vbe_sr, | |
922 | vbeSaveRestoreFunction function) | |
923 | { | |
924 | Bool SaveSucc = FALSE; | |
925 | ||
926 | if (VBE_VERSION_MAJOR(pVbe->version) > 1 | |
927 | && (function == MODE_SAVE || vbe_sr->pstate)) { | |
928 | if (function == MODE_RESTORE) | |
929 | memcpy(vbe_sr->state, vbe_sr->pstate, vbe_sr->stateSize); | |
930 | ErrorF("VBESaveRestore\n"); | |
931 | if ((VBESaveRestore(pVbe, function, | |
932 | (pointer) &vbe_sr->state, | |
933 | &vbe_sr->stateSize, &vbe_sr->statePage))) { | |
934 | if (function == MODE_SAVE) { | |
935 | SaveSucc = TRUE; | |
936 | vbe_sr->stateMode = -1; /* invalidate */ | |
937 | /* don't rely on the memory not being touched */ | |
938 | if (vbe_sr->pstate == NULL) | |
939 | vbe_sr->pstate = malloc(vbe_sr->stateSize); | |
940 | memcpy(vbe_sr->pstate, vbe_sr->state, vbe_sr->stateSize); | |
941 | } | |
942 | ErrorF("VBESaveRestore done with success\n"); | |
943 | return; | |
944 | } | |
945 | ErrorF("VBESaveRestore done\n"); | |
946 | } | |
947 | ||
948 | if (function == MODE_SAVE && !SaveSucc) | |
949 | (void) VBEGetVBEMode(pVbe, &vbe_sr->stateMode); | |
950 | ||
951 | if (function == MODE_RESTORE && vbe_sr->stateMode != -1) | |
952 | VBESetVBEMode(pVbe, vbe_sr->stateMode, NULL); | |
953 | ||
954 | } | |
955 | ||
956 | int | |
957 | VBEGetPixelClock(vbeInfoPtr pVbe, int mode, int clock) | |
958 | { | |
959 | /* | |
960 | Input: | |
961 | AX := 4F0Bh VBE Get Pixel Clock | |
962 | BL := 00h Get Pixel Clock | |
963 | ECX := pixel clock in units of Hz | |
964 | DX := mode number | |
965 | ||
966 | Output: | |
967 | AX := VBE Return Status | |
968 | ECX := Closest pixel clock | |
969 | */ | |
970 | ||
971 | pVbe->pInt10->num = 0x10; | |
972 | pVbe->pInt10->ax = 0x4f0b; | |
973 | pVbe->pInt10->bx = 0x00; | |
974 | pVbe->pInt10->cx = clock; | |
975 | pVbe->pInt10->dx = mode; | |
976 | xf86ExecX86int10(pVbe->pInt10); | |
977 | ||
978 | if (R16(pVbe->pInt10->ax) != 0x4f) | |
979 | return 0; | |
980 | ||
981 | return pVbe->pInt10->cx; | |
982 | } | |
983 | ||
984 | Bool | |
985 | VBEDPMSSet(vbeInfoPtr pVbe, int mode) | |
986 | { | |
987 | /* | |
988 | Input: | |
989 | AX := 4F10h DPMS | |
990 | BL := 01h Set Display Power State | |
991 | BH := requested power state | |
992 | ||
993 | Output: | |
994 | AX := VBE Return Status | |
995 | */ | |
996 | ||
997 | pVbe->pInt10->num = 0x10; | |
998 | pVbe->pInt10->ax = 0x4f10; | |
999 | pVbe->pInt10->bx = 0x01; | |
1000 | switch (mode) { | |
1001 | case DPMSModeOn: | |
1002 | break; | |
1003 | case DPMSModeStandby: | |
1004 | pVbe->pInt10->bx |= 0x100; | |
1005 | break; | |
1006 | case DPMSModeSuspend: | |
1007 | pVbe->pInt10->bx |= 0x200; | |
1008 | break; | |
1009 | case DPMSModeOff: | |
1010 | pVbe->pInt10->bx |= 0x400; | |
1011 | break; | |
1012 | } | |
1013 | xf86ExecX86int10(pVbe->pInt10); | |
1014 | return (R16(pVbe->pInt10->ax) == 0x4f); | |
1015 | } | |
1016 | ||
1017 | void | |
1018 | VBEInterpretPanelID(ScrnInfoPtr pScrn, struct vbePanelID *data) | |
1019 | { | |
1020 | DisplayModePtr mode; | |
1021 | const float PANEL_HZ = 60.0; | |
1022 | ||
1023 | if (!data) | |
1024 | return; | |
1025 | ||
1026 | xf86DrvMsg(pScrn->scrnIndex, X_INFO, "PanelID returned panel resolution %dx%d\n", | |
1027 | data->hsize, data->vsize); | |
1028 | ||
1029 | if (pScrn->monitor->nHsync || pScrn->monitor->nVrefresh) | |
1030 | return; | |
1031 | ||
1032 | if (data->hsize < 320 || data->vsize < 240) { | |
1033 | xf86DrvMsg(pScrn->scrnIndex, X_INFO, "...which I refuse to believe\n"); | |
1034 | return; | |
1035 | } | |
1036 | ||
1037 | mode = xf86CVTMode(data->hsize, data->vsize, PANEL_HZ, 1, 0); | |
1038 | ||
1039 | pScrn->monitor->nHsync = 1; | |
1040 | pScrn->monitor->hsync[0].lo = 29.37; | |
1041 | pScrn->monitor->hsync[0].hi = (float) mode->Clock / (float) mode->HTotal; | |
1042 | pScrn->monitor->nVrefresh = 1; | |
1043 | pScrn->monitor->vrefresh[0].lo = 56.0; | |
1044 | pScrn->monitor->vrefresh[0].hi = | |
1045 | (float) mode->Clock * 1000.0 / (float) mode->HTotal / | |
1046 | (float) mode->VTotal; | |
1047 | ||
1048 | if (pScrn->monitor->vrefresh[0].hi < 59.47) | |
1049 | pScrn->monitor->vrefresh[0].hi = 59.47; | |
1050 | ||
1051 | free(mode); | |
1052 | } | |
1053 | ||
1054 | struct vbePanelID * | |
1055 | VBEReadPanelID(vbeInfoPtr pVbe) | |
1056 | { | |
1057 | int RealOff = pVbe->real_mode_base; | |
1058 | pointer page = pVbe->memory; | |
1059 | void *tmp = NULL; | |
1060 | int screen = pVbe->pInt10->pScrn->scrnIndex; | |
1061 | ||
1062 | pVbe->pInt10->ax = 0x4F11; | |
1063 | pVbe->pInt10->bx = 0x01; | |
1064 | pVbe->pInt10->cx = 0; | |
1065 | pVbe->pInt10->dx = 0; | |
1066 | pVbe->pInt10->es = SEG_ADDR(RealOff); | |
1067 | pVbe->pInt10->di = SEG_OFF(RealOff); | |
1068 | pVbe->pInt10->num = 0x10; | |
1069 | ||
1070 | xf86ExecX86int10(pVbe->pInt10); | |
1071 | ||
1072 | if ((pVbe->pInt10->ax & 0xff) != 0x4f) { | |
1073 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE PanelID invalid\n"); | |
1074 | goto error; | |
1075 | } | |
1076 | ||
1077 | switch (pVbe->pInt10->ax & 0xff00) { | |
1078 | case 0x0: | |
1079 | xf86DrvMsgVerb(screen, X_INFO, 3, | |
1080 | "VESA VBE PanelID read successfully\n"); | |
1081 | tmp = xnfalloc(32); | |
1082 | memcpy(tmp, page, 32); | |
1083 | break; | |
1084 | case 0x100: | |
1085 | xf86DrvMsgVerb(screen, X_INFO, 3, "VESA VBE PanelID read failed\n"); | |
1086 | break; | |
1087 | default: | |
1088 | xf86DrvMsgVerb(screen, X_INFO, 3, | |
1089 | "VESA VBE PanelID unknown failure %i\n", | |
1090 | pVbe->pInt10->ax & 0xff00); | |
1091 | break; | |
1092 | } | |
1093 | ||
1094 | error: | |
1095 | return tmp; | |
1096 | } |