]> git.saurik.com Git - apple/bootx.git/blob - bootx.tproj/sl.subproj/display.c
000f2d606dac52fcd5f9a0034b912212b82be068
[apple/bootx.git] / bootx.tproj / sl.subproj / display.c
1 /*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22 /*
23 * display.c - Functions to manage and find displays.
24 *
25 * Copyright (c) 1998-2000 Apple Computer, Inc.
26 *
27 * DRI: Josh de Cesare
28 */
29
30 #include <sl.h>
31
32 #include "clut.h"
33
34 #include "bad_system.h"
35
36 #if kMacOSXServer
37 #include "images.h"
38 #else
39 #include "happy_mac.h"
40 #include "happy_foot.h"
41 #endif
42
43 struct DisplayInfo {
44 CICell screenPH;
45 CICell screenIH;
46 CICell address;
47 CICell width;
48 CICell height;
49 CICell depth;
50 CICell linebytes;
51 CICell triedToOpen;
52 };
53
54 typedef struct DisplayInfo DisplayInfo, *DisplayInfoPtr;
55
56 // The Driver Description
57 enum {
58 kInitialDriverDescriptor = 0,
59 kVersionOneDriverDescriptor = 1,
60 kTheDescriptionSignature = 'mtej',
61 kDriverDescriptionSignature = 'pdes'
62 };
63
64 struct DriverType {
65 unsigned char nameInfoStr[32]; // Driver Name/Info String
66 unsigned long version; // Driver Version Number - really NumVersion
67 };
68 typedef struct DriverType DriverType;
69
70 struct DriverDescription {
71 unsigned long driverDescSignature; // Signature field of this structure
72 unsigned long driverDescVersion; // Version of this data structure
73 DriverType driverType; // Type of Driver
74 char otherStuff[512];
75 };
76 typedef struct DriverDescription DriverDescription;
77
78 #define kNumNetDrivers (7)
79
80 char *gNetDriverFileNames[kNumNetDrivers] = {
81 "ATYRagePro_ndrv",
82 "Spinnaker_ndrv",
83 "ATYLT-G_ndrv",
84 "ATYLTPro_ndrv",
85 "chips65550_ndrv",
86 "control_ndrv",
87 "ATYRage_ndrv"
88 };
89
90 char *gNetDriverMatchNames[kNumNetDrivers] = {
91 "ATY,mach64_3DUPro",
92 "ATY,mach64",
93 "ATY,264LT-G",
94 "ATY,RageLTPro",
95 "chips65550",
96 "control",
97 "ATY,mach64_3DU"
98 };
99
100 static long FindDisplays(void);
101 static long OpenDisplays(void);
102 static void DumpDisplaysInfo(void);
103 static long OpenDisplay(long displayNum);
104 static long InitDisplay(long displayNum);
105 static long NetLoadDrivers(void);
106 static long LoadDisplayDriver(char *fileSpec);
107 static long LookUpCLUTIndex(long index, long depth);
108
109 #if 0
110 static long DiskLoadDrivers(void);
111 #endif
112
113 static long gNumDisplays;
114 static long gMainDisplayNum;
115 static DisplayInfo gDisplays[16];
116
117 // Public Functions
118
119 long InitDisplays(void)
120 {
121 FindDisplays();
122 OpenDisplays();
123
124 return 0;
125 }
126
127
128 long LoadDisplayDrivers(void)
129 {
130 long ret;
131
132 // Don't bother if there are no displays.
133 if (gNumDisplays == 0) return 0;
134
135 ret = NetLoadDrivers();
136
137 #if 0
138 switch (gBootFileType) {
139 case kNetworkDeviceType :
140 ret = NetLoadDrivers();
141 break;
142
143 case kBlockDeviceType :
144 ret = DiskLoadDrivers();
145 break;
146
147 case kUnknownDeviceType :
148 ret = 0;
149 }
150 #endif
151
152 return ret;
153 }
154
155
156 long DrawSplashScreen(void)
157 {
158 DisplayInfoPtr display;
159 unsigned char *happyMac, *happyFoot;
160 short *happyMac16, *happyFoot16;
161 long *happyMac32, *happyFoot32;
162 long cnt, x, y, pixelSize;
163
164 if (gMainDisplayNum == -1) return 0;
165
166 display = &gDisplays[gMainDisplayNum];
167
168 // Make sure the boot display is marked.
169 SetProp(display->screenPH, "AAPL,boot-display", NULL, 0);
170
171 #if kMacOSXServer
172 x = (display->width - BIG_WIDTH) / 2;
173 y = ((display->height - BIG_HEIGHT)) / 2 + BIG_DY;
174
175 CallMethod_5_0(display->screenIH, "draw-rectangle", (long)bigImage,
176 x, y, BIG_WIDTH, BIG_HEIGHT);
177
178 x = (display->width - SPIN_WIDTH) / 2;
179 y = ((display->height - SPIN_WIDTH) / 2) + 28;
180
181 // Set up the spin cursor.
182 SpinInit(0, display->screenIH, waitCursors,
183 x, y, SPIN_WIDTH, SPIN_WIDTH);
184
185 // Do a spin to start things off.
186 Spin();
187
188 #else
189
190 switch (display->depth) {
191 case 16 :
192 happyMac16 = malloc(kHappyMacWidth * kHappyMacHeight * 2);
193 for (cnt = 0; cnt < (kHappyMacWidth * kHappyMacHeight); cnt++)
194 happyMac16[cnt] = LookUpCLUTIndex(gHappyMacIcon[cnt], 16);
195 happyMac = (char *)happyMac16;
196 break;
197
198 case 32 :
199 happyMac32 = malloc(kHappyMacWidth * kHappyMacHeight * 4);
200 for (cnt = 0; cnt < (kHappyMacWidth * kHappyMacHeight); cnt++)
201 happyMac32[cnt] = LookUpCLUTIndex(gHappyMacIcon[cnt], 32);
202 happyMac = (char *)happyMac32;
203 break;
204
205 default :
206 happyMac = gHappyMacIcon;
207 break;
208 }
209
210 x = (display->width - kHappyMacWidth) / 2;
211 y = (display->height - kHappyMacHeight) / 2;
212
213 CallMethod_5_0(display->screenIH, "draw-rectangle", (long)happyMac,
214 x, y, kHappyMacWidth, kHappyMacHeight);
215
216 if (gBootFileType != kNetworkDeviceType) {
217 SpinInit(0, 0, NULL, 0, 0, 0, 0, 0);
218 } else {
219 Interpret_1_0("ms", 1000);
220
221 switch (display->depth) {
222 case 16 :
223 pixelSize = 2;
224 happyFoot16 = malloc(kHappyFootWidth * kHappyFootHeight * 2);
225 for (cnt = 0; cnt < (kHappyFootWidth * kHappyFootHeight); cnt++)
226 happyFoot16[cnt] = LookUpCLUTIndex(gHappyFootPict[cnt], 16);
227 happyFoot = (char *)happyFoot16;
228 break;
229
230 case 32 :
231 pixelSize = 4;
232 happyFoot32 = malloc(kHappyFootWidth * kHappyFootHeight * 4);
233 for (cnt = 0; cnt < (kHappyFootWidth * kHappyFootHeight); cnt++)
234 happyFoot32[cnt] = LookUpCLUTIndex(gHappyFootPict[cnt], 32);
235 happyFoot = (char *)happyFoot32;
236 break;
237
238 default :
239 pixelSize = 1;
240 happyFoot = gHappyFootPict;
241 break;
242 }
243
244 for (cnt = 0; cnt < kHappyFootHeight - 1; cnt++) {
245
246 CallMethod_5_0(display->screenIH, "draw-rectangle", (long)happyMac,
247 x, y - cnt, kHappyMacWidth, kHappyMacHeight);
248
249 CallMethod_5_0(display->screenIH, "draw-rectangle",
250 (long)happyFoot + pixelSize *
251 kHappyFootWidth * (kHappyFootHeight - cnt - 1),
252 x + 6, y + kHappyMacHeight - 1 - cnt,
253 kHappyFootWidth, cnt + 1);
254
255 CallMethod_5_0(display->screenIH, "draw-rectangle",
256 (long)happyFoot + pixelSize *
257 kHappyFootWidth * (kHappyFootHeight - cnt - 1),
258 x + 15, y + kHappyMacHeight - 1 - cnt,
259 kHappyFootWidth, cnt + 1);
260
261 Interpret_1_0("ms", 75);
262 }
263
264 // Set up the spin cursor.
265 SpinInit(1, display->screenIH, happyFoot,
266 x + 15, y + kHappyMacHeight - kHappyFootHeight + 1,
267 kHappyFootWidth, kHappyFootHeight, pixelSize);
268 }
269 #endif
270
271 return 0;
272 }
273
274
275 long DrawBrokenSystemFolder(void)
276 {
277 long cnt;
278 unsigned char *iconPtr, tmpIcon[1024];
279 short *icon16;
280 long *icon32;
281 DisplayInfoPtr display = &gDisplays[gMainDisplayNum];
282
283 long x, y;
284
285 #if kMacOSXServer
286 // Set the screen to Medium Blue
287 CallMethod_5_0(display->screenIH, "fill-rectangle", 128, 0, 0,
288 display->width, display->height);
289
290 // Use the default icon.
291 iconPtr = gBrokenSystemFolderIcon;
292 #else
293 // Set the screen to Medium Grey
294 CallMethod_5_0(display->screenIH, "fill-rectangle",
295 LookUpCLUTIndex(0xF9, display->depth),
296 0, 0, display->width, display->height);
297
298 // Convert the default icon.
299 for (cnt = 0; cnt < 1024; cnt++) {
300 tmpIcon[cnt] = gBrokenSystemFolderIcon[cnt];
301 if (tmpIcon[cnt] == 0x80) tmpIcon[cnt] = 0xF9;
302 }
303 iconPtr = tmpIcon;
304 #endif
305
306 switch (display->depth) {
307 case 16 :
308 icon16 = malloc(32 * 32 * 2);
309 for (cnt = 0; cnt < (32 * 32); cnt++)
310 icon16[cnt] = LookUpCLUTIndex(iconPtr[cnt], 16);
311 iconPtr = (char *)icon16;
312 break;
313
314 case 32 :
315 icon32 = malloc(32 * 32 * 4);
316 for (cnt = 0; cnt < (32 * 32); cnt++)
317 icon32[cnt] = LookUpCLUTIndex(iconPtr[cnt], 32);
318 iconPtr = (char *)icon32;
319 break;
320
321 default :
322 break;
323 }
324
325 // Draw the broken system folder.
326 x = (display->width - 32) / 2;
327 y = ((display->height - 32)) / 2;
328 CallMethod_5_0(display->screenIH, "draw-rectangle",
329 (long)iconPtr, x, y, 32, 32);
330
331 return 0;
332 }
333
334
335 void GetMainScreenPH(Boot_Video_Ptr video)
336 {
337 DisplayInfoPtr display;
338
339 if (gMainDisplayNum == -1) {
340 // No display, set it to zero.
341 video->v_baseAddr = 0;
342 video->v_rowBytes = 0;
343 video->v_width = 0;
344 video->v_height = 0;
345 video->v_depth = 0;
346 } else {
347 display = &gDisplays[gMainDisplayNum];
348
349 video->v_baseAddr = display->address;
350 video->v_rowBytes = display->linebytes;
351 video->v_width = display->width;
352 video->v_height = display->height;
353 video->v_depth = display->depth;
354 }
355 }
356
357 // Private Functions
358
359 static long FindDisplays(void)
360 {
361 CICell screenPH, controlPH;
362 long cnt;
363
364 // Find all the screens in the system.
365 screenPH = 0;
366 while (1) {
367 screenPH = SearchForNode(screenPH, 1, "device_type", "display");
368 if (screenPH != 0) gDisplays[gNumDisplays++].screenPH = screenPH;
369 else break;
370 }
371
372 // Find /chaos/control, and
373 // invalidate gStdOutPH if equal (since new OF was downloaded).
374 controlPH = FindDevice("/chaos/control");
375 if (gStdOutPH == controlPH) gStdOutPH = 0;
376
377 // Find the main screen using the screen alias or chaos/control.
378 gMainDisplayNum = -1;
379 screenPH = FindDevice("screen");
380 if (screenPH == -1) screenPH = controlPH;
381 for (cnt = 0; cnt < gNumDisplays; cnt++)
382 if (gDisplays[cnt].screenPH == screenPH) gMainDisplayNum = cnt;
383
384 return 0;
385 }
386
387
388 static long OpenDisplays(void)
389 {
390 long cnt;
391
392 // Open the main screen or
393 // look for a main screen if we don't have one.
394 if ((gMainDisplayNum == -1) || !OpenDisplay(gMainDisplayNum)) {
395 gMainDisplayNum = -1;
396 for (cnt = 0; cnt < gNumDisplays; cnt++) {
397 if (OpenDisplay(cnt)) {
398 gMainDisplayNum = cnt;
399 break;
400 }
401 }
402 }
403
404 // Open the rest of the displays
405 if (gOFVersion >= kOFVersion3x) {
406 for (cnt = 0; cnt < gNumDisplays; cnt++) {
407 OpenDisplay(cnt);
408 }
409 }
410
411 return 0;
412 }
413
414 static void DumpDisplaysInfo(void)
415 {
416 long cnt, length;
417 char tmpStr[512];
418
419 printf("gNumDisplays: %x, gMainDisplayNum: %x\n",
420 gNumDisplays, gMainDisplayNum);
421
422 for (cnt = 0; cnt < gNumDisplays; cnt++) {
423 printf("Display: %x, screenPH: %x, screenIH: %x\n",
424 cnt, gDisplays[cnt].screenPH, gDisplays[cnt].screenIH);
425
426 if (gDisplays[cnt].screenPH) {
427 length = PackageToPath(gDisplays[cnt].screenPH, tmpStr, 511);
428 tmpStr[length] = '\0';
429 printf("PHandle Path: %s\n", tmpStr);
430 }
431
432 if (gDisplays[cnt].screenIH) {
433 length = InstanceToPath(gDisplays[cnt].screenIH, tmpStr, 511);
434 tmpStr[length] = '\0';
435 printf("IHandle Path: %s\n", tmpStr);
436 }
437
438 printf("address = %x\n", gDisplays[cnt].address);
439 printf("linebytes = %x\n", gDisplays[cnt].linebytes);
440 printf("width = %x\n", gDisplays[cnt].width);
441 printf("height = %x\n", gDisplays[cnt].height);
442 printf("depth = %x\n", gDisplays[cnt].depth);
443 printf("\n");
444 }
445 }
446
447
448 static long OpenDisplay(long displayNum)
449 {
450 char screenPath[258];
451 CICell screenIH;
452 long ret;
453
454 // Only try to open a screen once.
455 if (gDisplays[displayNum].triedToOpen) {
456 return gDisplays[displayNum].screenIH != 0;
457 } else {
458 gDisplays[displayNum].triedToOpen = -1;
459 }
460
461 // Try to use mac-boot's ihandle.
462 Interpret_0_1("\" _screen-ihandle\" $find if execute else 0 then",
463 &screenIH);
464 if ((screenIH != 0) &&
465 (InstanceToPackage(screenIH) != gDisplays[displayNum].screenPH)) {
466 screenIH = 0;
467 }
468
469 // Try to use stdout as the screen's ihandle
470 if ((screenIH == 0) && (gStdOutPH == gDisplays[displayNum].screenPH)) {
471 screenIH = gStdOutIH;
472 }
473
474 // Try to open the display.
475 if (screenIH == 0) {
476 screenPath[255] = '\0';
477 ret = PackageToPath(gDisplays[displayNum].screenPH, screenPath, 255);
478 if (ret != -1) {
479 strcat(screenPath, ":0");
480 screenIH = Open(screenPath);
481 }
482 }
483
484 // Save the ihandle for later use.
485 gDisplays[displayNum].screenIH = screenIH;
486
487 // Initialize the display.
488 if (screenIH != 0) InitDisplay(displayNum);
489
490 return screenIH != 0;
491 }
492
493
494 static long InitDisplay(long displayNum)
495 {
496 DisplayInfoPtr display = &gDisplays[displayNum];
497 CICell screenPH = display->screenPH;
498 CICell screenIH = display->screenIH;
499
500 // Get the vital info for this screen.
501 GetProp(screenPH, "address", (char *)&(display->address), 4);
502 GetProp(screenPH, "width", (char *)&(display->width), 4);
503 GetProp(screenPH, "height", (char *)&(display->height), 4);
504 GetProp(screenPH, "depth", (char *)&(display->depth), 4);
505 GetProp(screenPH, "linebytes", (char *)&(display->linebytes), 4);
506
507 // Replace some of the drivers words.
508 Interpret_3_1(
509 " to active-package"
510 " value rowbytes"
511 " value depthbytes"
512 " frame-buffer-adr value this-frame-buffer-adr"
513
514 " : rect-setup" // ( adr|index x y w h -- w adr|index xy-adr h )
515 " >r >r rowbytes * swap depthbytes * + this-frame-buffer-adr +"
516 " r> depthbytes * -rot r>"
517 " ;"
518
519 " : DRAW-RECTANGLE" // ( adr x y w h -- )
520 " rect-setup" // ( w adr xy-adr h )
521 " 0 ?do" // ( w adr xy-adr )
522 " 2dup 4 pick move"
523 " 2 pick rowbytes d+"
524 " loop"
525 " 3drop"
526 " ;"
527
528 " : FILL-RECTANGLE" // ( index x y w h -- )
529 " rect-setup rot depthbytes case"
530 " 1 of dup 8 << or dup 10 << or endof"
531 " 2 of dup 10 << or endof"
532 " endcase -rot 0 ?do"
533 " dup 3 pick 3 pick filll"
534 " rowbytes +"
535 " loop"
536 " 3drop"
537 " ;"
538
539 " : READ-RECTANGLE" // ( adr x y w h -- )
540 " rect-setup >r swap r> 0 ?do"
541 " 2dup 4 pick move"
542 " rowbytes 3 pick d+"
543 " loop"
544 " 3drop"
545 " ;"
546
547 " this-frame-buffer-adr"
548 " 0 to active-package"
549 , display->screenPH, display->linebytes,
550 display->depth / 8, &display->address);
551
552 // Set the CLUT for 8 bit displays
553 if (display->depth == 8) {
554 CallMethod_3_0(screenIH, "set-colors", (long)gClut, 0, 256);
555 }
556
557 #if kMacOSXServer
558 // Set the screen to Medium Blue
559 CallMethod_5_0(screenIH, "fill-rectangle", 128, 0, 0,
560 display->width, display->height);
561 #else
562 // Set the screen to Medium Grey
563 CallMethod_5_0(screenIH, "fill-rectangle",
564 LookUpCLUTIndex(0xF9, display->depth),
565 0, 0, display->width, display->height);
566 #endif
567
568 return 0;
569 }
570
571
572 static long NetLoadDrivers(void)
573 {
574 long ret, cnt, curDisplay;
575 CICell screenPH;
576 char fileSpec[512];
577
578 for (cnt = 0; cnt < kNumNetDrivers; cnt++) {
579
580 // See if there is a display for this driver.
581 for (curDisplay = 0; curDisplay < gNumDisplays; curDisplay++) {
582 screenPH = gDisplays[curDisplay].screenPH;
583 if (MatchThis(screenPH, gNetDriverMatchNames[cnt]) == 0) break;
584 }
585
586 if (curDisplay == gNumDisplays) continue;
587
588 sprintf(fileSpec, "%s%sDrivers\\ppc\\IONDRV.config\\%s",
589 gRootDir,
590 (gBootFileType == kNetworkDeviceType) ? "" : "private\\",
591 gNetDriverFileNames[cnt]);
592
593 ret = LoadDisplayDriver(fileSpec);
594 }
595
596 return 0;
597 }
598
599 static long LoadDisplayDriver(char *fileSpec)
600 {
601 char *pef, *currentPef, *buffer;
602 long pefLen, currentPefLen, ndrvUsed;
603 long curDisplay;
604 char descripName[] = " TheDriverDescription";
605 long err;
606 DriverDescription descrip;
607 DriverDescription curDesc;
608 char matchName[40];
609 unsigned long newVersion;
610 unsigned long curVersion;
611 CICell screenPH;
612
613 pefLen = LoadFile(fileSpec);
614 if (pefLen == -1) return -1;
615 if (pefLen == 0) return 0;
616
617 pef = (char *)kLoadAddr;
618
619 descripName[0] = strlen(descripName + 1);
620 err = GetSymbolFromPEF(descripName, pef, &descrip, sizeof(descrip));
621 if(err != 0) {
622 printf("\nGetSymbolFromPEF returns %d\n",err);
623 return -1;
624 }
625 if((descrip.driverDescSignature != kTheDescriptionSignature) ||
626 (descrip.driverDescVersion != kInitialDriverDescriptor))
627 return 0;
628
629 strncpy(matchName, descrip.driverType.nameInfoStr + 1,
630 descrip.driverType.nameInfoStr[0]);
631 newVersion = descrip.driverType.version;
632
633 if((newVersion & 0xffff) == 0x8000) // final stage, release rev
634 newVersion |= 0xff;
635
636 ndrvUsed = 0;
637 buffer = (char *)malloc(pefLen);
638 if (buffer == NULL) {
639 printf("No space for the NDRV\n");
640 return -1;
641 }
642 bcopy(pef, buffer, pefLen);
643
644 for (curDisplay = 0; curDisplay < gNumDisplays; curDisplay++) {
645 screenPH = gDisplays[curDisplay].screenPH;
646
647 if (MatchThis(screenPH, matchName) != 0) continue;
648
649 err = GetPackageProperty(screenPH, "driver,AAPL,MacOS,PowerPC",
650 &currentPef, &currentPefLen);
651
652 if (err == 0) {
653 err = GetSymbolFromPEF(descripName,currentPef,&curDesc,sizeof(curDesc));
654 if (err != 0) {
655 if((curDesc.driverDescSignature == kTheDescriptionSignature) &&
656 (curDesc.driverDescVersion == kInitialDriverDescriptor)) {
657 curVersion = curDesc.driverType.version;
658 if((curVersion & 0xffff) == 0x8000) // final stage, release rev
659 curVersion |= 0xff;
660
661 if( newVersion <= curVersion)
662 pefLen = 0;
663 }
664 }
665 }
666
667 if(pefLen == 0) continue;
668
669 printf("Installing patch driver\n");
670
671 SetProp(screenPH, "driver,AAPL,MacOS,PowerPC", buffer, pefLen);
672 ndrvUsed = 1;
673 }
674
675 if (ndrvUsed == 0) free(buffer);
676
677 return 0;
678 }
679
680
681 static long LookUpCLUTIndex(long index, long depth)
682 {
683 long result, red, green, blue;
684
685 red = gClut[index * 3 + 0];
686 green = gClut[index * 3 + 1];
687 blue = gClut[index * 3 + 2];
688
689 switch (depth) {
690 case 16 :
691 result = ((red & 0xF8) << 7)|((green & 0xF8) << 2)|((blue & 0xF8) >> 3);
692 break;
693
694 case 32 :
695 result = (red << 16) | (green << 8) | blue;
696 break;
697
698 default :
699 result = index;
700 break;
701 }
702
703 return result;
704 }
705
706 #if 0
707 static long DiskLoadDrivers(void)
708 {
709 long ret, flags, index, time;
710 char dirSpec[512], *name;
711
712 index = 0;
713 while (1) {
714 sprintf(dirSpec, "%sprivate\\Drivers\\ppc\\IONDRV.config\\", gRootDir);
715
716 ret = GetDirEntry(dirSpec, &index, &name, &flags, &time);
717 if (ret == -1) break;
718
719 if (flags != kFlatFileType) continue;
720
721 strcat(dirSpec, name);
722 ret = LoadDisplayDriver(dirSpec);
723
724 if (ret == -1) return -1;
725 }
726
727 return 0;
728 }
729 #endif