]> git.proxmox.com Git - mirror_edk2.git/blob - MdeModulePkg/Universal/BdsDxe/Hotkey.c
Coding style checked
[mirror_edk2.git] / MdeModulePkg / Universal / BdsDxe / Hotkey.c
1 /** @file
2 Provides a way for 3rd party applications to register themselves for launch by the
3 Boot Manager based on hot key
4
5 Copyright (c) 2007 - 2008, Intel Corporation. <BR>
6 All rights reserved. This program and the accompanying materials
7 are licensed and made available under the terms and conditions of the BSD License
8 which accompanies this distribution. The full text of the license may be found at
9 http://opensource.org/licenses/bsd-license.php
10
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13
14 **/
15
16 #include "Hotkey.h"
17
18
19 LIST_ENTRY mHotkeyList = INITIALIZE_LIST_HEAD_VARIABLE (mHotkeyList);
20 BOOLEAN mHotkeyCallbackPending = FALSE;
21 EFI_EVENT mHotkeyEvent;
22 VOID *mHotkeyRegistration;
23
24
25 /**
26 Check if the Key Option is valid or not.
27
28 @param KeyOption The Hot Key Option to be checked.
29
30 @retval TRUE The Hot Key Option is valid.
31 @retval FALSE The Hot Key Option is invalid.
32
33 **/
34 BOOLEAN
35 IsKeyOptionValid (
36 IN EFI_KEY_OPTION *KeyOption
37 )
38 {
39 UINT16 BootOptionName[10];
40 UINT8 *BootOptionVar;
41 UINTN BootOptionSize;
42 UINT32 Crc;
43
44 //
45 // Check whether corresponding Boot Option exist
46 //
47 UnicodeSPrint (BootOptionName, sizeof (BootOptionName), L"Boot%04x", KeyOption->BootOption);
48 BootOptionVar = BdsLibGetVariableAndSize (
49 BootOptionName,
50 &gEfiGlobalVariableGuid,
51 &BootOptionSize
52 );
53
54 if (BootOptionVar == NULL || BootOptionSize == 0) {
55 return FALSE;
56 }
57
58 //
59 // Check CRC for Boot Option
60 //
61 gBS->CalculateCrc32 (BootOptionVar, BootOptionSize, &Crc);
62 FreePool (BootOptionVar);
63
64 return (BOOLEAN) ((KeyOption->BootOptionCrc == Crc) ? TRUE : FALSE);
65 }
66
67 /**
68 Create Key#### for the given hotkey.
69
70 @param KeyOption The Hot Key Option to be added.
71 @param KeyOptionNumber The key option number for Key#### (optional).
72
73 @retval EFI_SUCCESS Register hotkey successfully.
74 @retval EFI_INVALID_PARAMETER The hotkey option is invalid.
75 @retval EFI_OUT_OF_RESOURCES Fail to allocate memory resource.
76
77 **/
78 EFI_STATUS
79 RegisterHotkey (
80 IN EFI_KEY_OPTION *KeyOption,
81 OUT UINT16 *KeyOptionNumber
82 )
83 {
84 UINT16 KeyOptionName[10];
85 UINT16 *KeyOrder;
86 UINTN KeyOrderSize;
87 UINT16 *NewKeyOrder;
88 UINTN Index;
89 UINT16 MaxOptionNumber;
90 UINT16 RegisterOptionNumber;
91 EFI_KEY_OPTION *TempOption;
92 UINTN TempOptionSize;
93 EFI_STATUS Status;
94 UINTN KeyOptionSize;
95 BOOLEAN UpdateBootOption;
96
97 //
98 // Validate the given key option
99 //
100 if (!IsKeyOptionValid (KeyOption)) {
101 return EFI_INVALID_PARAMETER;
102 }
103
104 KeyOptionSize = sizeof (EFI_KEY_OPTION) + GET_KEY_CODE_COUNT (KeyOption->KeyData.PackedValue) * sizeof (EFI_INPUT_KEY);
105 UpdateBootOption = FALSE;
106
107 //
108 // check whether HotKey conflict with keys used by Setup Browser
109 //
110 KeyOrder = BdsLibGetVariableAndSize (
111 VAR_KEY_ORDER,
112 &gEfiGlobalVariableGuid,
113 &KeyOrderSize
114 );
115 if (KeyOrder == NULL) {
116 KeyOrderSize = 0;
117 }
118
119 //
120 // Find free key option number
121 //
122 MaxOptionNumber = 0;
123 TempOption = NULL;
124 for (Index = 0; Index < KeyOrderSize / sizeof (UINT16); Index++) {
125 if (MaxOptionNumber < KeyOrder[Index]) {
126 MaxOptionNumber = KeyOrder[Index];
127 }
128
129 UnicodeSPrint (KeyOptionName, sizeof (KeyOptionName), L"Key%04x", KeyOrder[Index]);
130 TempOption = BdsLibGetVariableAndSize (
131 KeyOptionName,
132 &gEfiGlobalVariableGuid,
133 &TempOptionSize
134 );
135
136 if (CompareMem (TempOption, KeyOption, TempOptionSize) == 0) {
137 //
138 // Got the option, so just return
139 //
140 FreePool (TempOption);
141 FreePool (KeyOrder);
142 return EFI_SUCCESS;
143 }
144
145 if (KeyOption->KeyData.PackedValue == TempOption->KeyData.PackedValue) {
146 if (GET_KEY_CODE_COUNT (KeyOption->KeyData.PackedValue) == 0 ||
147 CompareMem (
148 ((UINT8 *) TempOption) + sizeof (EFI_KEY_OPTION),
149 ((UINT8 *) KeyOption) + sizeof (EFI_KEY_OPTION),
150 KeyOptionSize - sizeof (EFI_KEY_OPTION)
151 ) == 0) {
152 //
153 // Hotkey is the same but BootOption changed, need update
154 //
155 UpdateBootOption = TRUE;
156 break;
157 }
158 }
159
160 FreePool (TempOption);
161 }
162
163 if (UpdateBootOption) {
164 RegisterOptionNumber = KeyOrder[Index];
165 FreePool (TempOption);
166 } else {
167 RegisterOptionNumber = (UINT16) (MaxOptionNumber + 1);
168 }
169
170 if (KeyOptionNumber != NULL) {
171 *KeyOptionNumber = RegisterOptionNumber;
172 }
173
174 //
175 // Create variable Key####
176 //
177 UnicodeSPrint (KeyOptionName, sizeof (KeyOptionName), L"Key%04x", RegisterOptionNumber);
178 Status = gRT->SetVariable (
179 KeyOptionName,
180 &gEfiGlobalVariableGuid,
181 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
182 KeyOptionSize,
183 KeyOption
184 );
185 if (EFI_ERROR (Status)) {
186 FreePool (KeyOrder);
187 return Status;
188 }
189
190 //
191 // Update the key order variable - "KeyOrder"
192 //
193 if (!UpdateBootOption) {
194 Index = KeyOrderSize / sizeof (UINT16);
195 KeyOrderSize += sizeof (UINT16);
196 }
197
198 NewKeyOrder = AllocatePool (KeyOrderSize);
199 if (NewKeyOrder == NULL) {
200 FreePool (KeyOrder);
201 return EFI_OUT_OF_RESOURCES;
202 }
203
204 if (KeyOrder != NULL) {
205 CopyMem (NewKeyOrder, KeyOrder, KeyOrderSize);
206 }
207
208 NewKeyOrder[Index] = RegisterOptionNumber;
209
210 Status = gRT->SetVariable (
211 VAR_KEY_ORDER,
212 &gEfiGlobalVariableGuid,
213 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
214 KeyOrderSize,
215 NewKeyOrder
216 );
217
218 FreePool (KeyOrder);
219 FreePool (NewKeyOrder);
220
221 return Status;
222 }
223
224 /**
225
226 Delete Key#### for the given Key Option number.
227
228 @param KeyOptionNumber Key option number for Key####
229
230 @retval EFI_SUCCESS Unregister hotkey successfully.
231 @retval EFI_NOT_FOUND No Key#### is found for the given Key Option number.
232
233 **/
234 EFI_STATUS
235 UnregisterHotkey (
236 IN UINT16 KeyOptionNumber
237 )
238 {
239 UINT16 KeyOption[10];
240 UINTN Index;
241 EFI_STATUS Status;
242 UINTN Index2Del;
243 UINT16 *KeyOrder;
244 UINTN KeyOrderSize;
245
246 //
247 // Delete variable Key####
248 //
249 UnicodeSPrint (KeyOption, sizeof (KeyOption), L"Key%04x", KeyOptionNumber);
250 gRT->SetVariable (
251 KeyOption,
252 &gEfiGlobalVariableGuid,
253 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
254 0,
255 NULL
256 );
257
258 //
259 // Adjust key order array
260 //
261 KeyOrder = BdsLibGetVariableAndSize (
262 VAR_KEY_ORDER,
263 &gEfiGlobalVariableGuid,
264 &KeyOrderSize
265 );
266 if (KeyOrder == NULL) {
267 return EFI_SUCCESS;
268 }
269
270 Index2Del = 0;
271 for (Index = 0; Index < KeyOrderSize / sizeof (UINT16); Index++) {
272 if (KeyOrder[Index] == KeyOptionNumber) {
273 Index2Del = Index;
274 break;
275 }
276 }
277
278 if (Index != KeyOrderSize / sizeof (UINT16)) {
279 //
280 // KeyOptionNumber found in "KeyOrder", delete it
281 //
282 for (Index = Index2Del; Index < KeyOrderSize / sizeof (UINT16) - 1; Index++) {
283 KeyOrder[Index] = KeyOrder[Index + 1];
284 }
285
286 KeyOrderSize -= sizeof (UINT16);
287 }
288
289 Status = gRT->SetVariable (
290 VAR_KEY_ORDER,
291 &gEfiGlobalVariableGuid,
292 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
293 KeyOrderSize,
294 KeyOrder
295 );
296
297 FreePool (KeyOrder);
298
299 return Status;
300 }
301
302 /**
303
304 This is the common notification function for HotKeys, it will be registered
305 with SimpleTextInEx protocol interface - RegisterKeyNotify() of ConIn handle.
306
307 @param KeyData A pointer to a buffer that is filled in with the keystroke
308 information for the key that was pressed.
309
310 @retval EFI_SUCCESS KeyData is successfully processed.
311
312 **/
313 EFI_STATUS
314 HotkeyCallback (
315 IN EFI_KEY_DATA *KeyData
316 )
317 {
318 BOOLEAN HotkeyCatched;
319 LIST_ENTRY BootLists;
320 LIST_ENTRY *Link;
321 BDS_HOTKEY_OPTION *Hotkey;
322 UINT16 Buffer[10];
323 BDS_COMMON_OPTION *BootOption;
324 UINTN ExitDataSize;
325 CHAR16 *ExitData;
326 EFI_STATUS Status;
327 EFI_KEY_DATA *HotkeyData;
328
329 if (mHotkeyCallbackPending) {
330 //
331 // When responsing to a Hotkey, ignore sequential hotkey stroke until
332 // the current Boot#### load option returned
333 //
334 return EFI_SUCCESS;
335 }
336
337 Status = EFI_SUCCESS;
338 Link = GetFirstNode (&mHotkeyList);
339
340 while (!IsNull (&mHotkeyList, Link)) {
341 HotkeyCatched = FALSE;
342 Hotkey = BDS_HOTKEY_OPTION_FROM_LINK (Link);
343
344 //
345 // Is this Key Stroke we are waiting for?
346 //
347 HotkeyData = &Hotkey->KeyData[Hotkey->WaitingKey];
348 if ((KeyData->Key.ScanCode == HotkeyData->Key.ScanCode) &&
349 (KeyData->Key.UnicodeChar == HotkeyData->Key.UnicodeChar) &&
350 ((HotkeyData->KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) ? (KeyData->KeyState.KeyShiftState == HotkeyData->KeyState.KeyShiftState) : 1)) {
351 //
352 // Receive an expecting key stroke
353 //
354 if (Hotkey->CodeCount > 1) {
355 //
356 // For hotkey of key combination, transit to next waiting state
357 //
358 Hotkey->WaitingKey++;
359
360 if (Hotkey->WaitingKey == Hotkey->CodeCount) {
361 //
362 // Received the whole key stroke sequence
363 //
364 HotkeyCatched = TRUE;
365 }
366 } else {
367 //
368 // For hotkey of single key stroke
369 //
370 HotkeyCatched = TRUE;
371 }
372 } else {
373 //
374 // Receive an unexpected key stroke, reset to initial waiting state
375 //
376 Hotkey->WaitingKey = 0;
377 }
378
379 if (HotkeyCatched) {
380 //
381 // Reset to initial waiting state
382 //
383 Hotkey->WaitingKey = 0;
384
385 //
386 // Launch its BootOption
387 //
388 InitializeListHead (&BootLists);
389
390 UnicodeSPrint (Buffer, sizeof (Buffer), L"Boot%04x", Hotkey->BootOptionNumber);
391 BootOption = BdsLibVariableToOption (&BootLists, Buffer);
392 BootOption->BootCurrent = Hotkey->BootOptionNumber;
393 BdsLibConnectDevicePath (BootOption->DevicePath);
394
395 //
396 // Clear the screen before launch this BootOption
397 //
398 gST->ConOut->Reset (gST->ConOut, FALSE);
399
400 mHotkeyCallbackPending = TRUE;
401 Status = BdsLibBootViaBootOption (BootOption, BootOption->DevicePath, &ExitDataSize, &ExitData);
402 mHotkeyCallbackPending = FALSE;
403
404 if (EFI_ERROR (Status)) {
405 //
406 // Call platform action to indicate the boot fail
407 //
408 BootOption->StatusString = GetStringById (STRING_TOKEN (STR_BOOT_FAILED));
409 PlatformBdsBootFail (BootOption, Status, ExitData, ExitDataSize);
410 } else {
411 //
412 // Call platform action to indicate the boot success
413 //
414 BootOption->StatusString = GetStringById (STRING_TOKEN (STR_BOOT_SUCCEEDED));
415 PlatformBdsBootSuccess (BootOption);
416 }
417 }
418
419 Link = GetNextNode (&mHotkeyList, Link);
420 }
421
422 return Status;
423 }
424
425 /**
426 Register the common HotKey notify function to given SimpleTextInEx protocol instance.
427
428 @param SimpleTextInEx Simple Text Input Ex protocol instance
429
430 @retval EFI_SUCCESS Register hotkey notification function successfully.
431 @retval EFI_OUT_OF_RESOURCES Unable to allocate necessary data structures.
432
433 **/
434 EFI_STATUS
435 HotkeyRegisterNotify (
436 IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleTextInEx
437 )
438 {
439 UINTN Index;
440 EFI_STATUS Status;
441 LIST_ENTRY *Link;
442 BDS_HOTKEY_OPTION *Hotkey;
443
444 //
445 // Register notification function for each hotkey
446 //
447 Link = GetFirstNode (&mHotkeyList);
448
449 while (!IsNull (&mHotkeyList, Link)) {
450 Hotkey = BDS_HOTKEY_OPTION_FROM_LINK (Link);
451
452 Index = 0;
453 do {
454 Status = SimpleTextInEx->RegisterKeyNotify (
455 SimpleTextInEx,
456 &Hotkey->KeyData[Index],
457 HotkeyCallback,
458 &Hotkey->NotifyHandle
459 );
460 if (EFI_ERROR (Status)) {
461 //
462 // some of the hotkey registry failed
463 //
464 return Status;
465 }
466 Index ++;
467 } while (Index < Hotkey->CodeCount);
468
469 Link = GetNextNode (&mHotkeyList, Link);
470 }
471
472 return EFI_SUCCESS;
473 }
474
475 /**
476 Callback function for SimpleTextInEx protocol install events
477
478 @param Event the event that is signaled.
479 @param Context not used here.
480
481 **/
482 VOID
483 EFIAPI
484 HotkeyEvent (
485 IN EFI_EVENT Event,
486 IN VOID *Context
487 )
488 {
489 EFI_STATUS Status;
490 UINTN BufferSize;
491 EFI_HANDLE Handle;
492 EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleTextInEx;
493
494 while (TRUE) {
495 BufferSize = sizeof (EFI_HANDLE);
496 Status = gBS->LocateHandle (
497 ByRegisterNotify,
498 NULL,
499 mHotkeyRegistration,
500 &BufferSize,
501 &Handle
502 );
503 if (EFI_ERROR (Status)) {
504 //
505 // If no more notification events exist
506 //
507 return ;
508 }
509
510 Status = gBS->HandleProtocol (
511 Handle,
512 &gEfiSimpleTextInputExProtocolGuid,
513 (VOID **) &SimpleTextInEx
514 );
515 ASSERT_EFI_ERROR (Status);
516
517 HotkeyRegisterNotify (SimpleTextInEx);
518 }
519 }
520
521 /**
522 Insert Key Option to hotkey list.
523
524 @param KeyOption The Hot Key Option to be added to hotkey list.
525
526 @retval EFI_SUCCESS Add to hotkey list success.
527 @retval EFI_OUT_OF_RESOURCES Fail to allocate memory resource.
528 **/
529 EFI_STATUS
530 HotkeyInsertList (
531 IN EFI_KEY_OPTION *KeyOption
532 )
533 {
534 BDS_HOTKEY_OPTION *HotkeyLeft;
535 BDS_HOTKEY_OPTION *HotkeyRight;
536 UINTN Index;
537 UINT32 KeyOptions;
538 UINT32 KeyShiftStateLeft;
539 UINT32 KeyShiftStateRight;
540 EFI_INPUT_KEY *InputKey;
541 EFI_KEY_DATA *KeyData;
542
543 HotkeyLeft = AllocateZeroPool (sizeof (BDS_HOTKEY_OPTION));
544 if (HotkeyLeft == NULL) {
545 return EFI_OUT_OF_RESOURCES;
546 }
547
548 HotkeyLeft->Signature = BDS_HOTKEY_OPTION_SIGNATURE;
549 HotkeyLeft->BootOptionNumber = KeyOption->BootOption;
550
551 KeyOptions = KeyOption->KeyData.PackedValue;
552
553 HotkeyLeft->CodeCount = (UINT8) GET_KEY_CODE_COUNT (KeyOptions);
554
555 //
556 // Map key shift state from KeyOptions to EFI_KEY_DATA.KeyState
557 //
558 KeyShiftStateRight = (KeyOptions & EFI_KEY_OPTION_SHIFT) |
559 ((KeyOptions & EFI_KEY_OPTION_CONTROL) << 1) |
560 ((KeyOptions & EFI_KEY_OPTION_ALT) << 2) |
561 ((KeyOptions & EFI_KEY_OPTION_LOGO) << 3) |
562 ((KeyOptions & (EFI_KEY_OPTION_MENU | EFI_KEY_OPTION_SYSREQ)) << 4) |
563 EFI_SHIFT_STATE_VALID;
564
565 KeyShiftStateLeft = (KeyShiftStateRight & 0xffffff00) | ((KeyShiftStateRight & 0xff) << 1);
566
567 InputKey = (EFI_INPUT_KEY *) (((UINT8 *) KeyOption) + sizeof (EFI_KEY_OPTION));
568
569 Index = 0;
570 KeyData = &HotkeyLeft->KeyData[0];
571 do {
572 //
573 // If Key CodeCount is 0, then only KeyData[0] is used;
574 // if Key CodeCount is n, then KeyData[0]~KeyData[n-1] are used
575 //
576 KeyData->Key.ScanCode = InputKey[Index].ScanCode;
577 KeyData->Key.UnicodeChar = InputKey[Index].UnicodeChar;
578 KeyData->KeyState.KeyShiftState = KeyShiftStateLeft;
579
580 Index++;
581 KeyData++;
582 } while (Index < HotkeyLeft->CodeCount);
583 InsertTailList (&mHotkeyList, &HotkeyLeft->Link);
584
585 if (KeyShiftStateLeft != KeyShiftStateRight) {
586 //
587 // Need an extra hotkey for shift key on right
588 //
589 HotkeyRight = AllocateCopyPool (sizeof (BDS_HOTKEY_OPTION), HotkeyLeft);
590 if (HotkeyRight == NULL) {
591 return EFI_OUT_OF_RESOURCES;
592 }
593
594 Index = 0;
595 KeyData = &HotkeyRight->KeyData[0];
596 do {
597 //
598 // Key.ScanCode and Key.UnicodeChar have already been initialized,
599 // only need to update KeyState.KeyShiftState
600 //
601 KeyData->KeyState.KeyShiftState = KeyShiftStateRight;
602
603 Index++;
604 KeyData++;
605 } while (Index < HotkeyRight->CodeCount);
606 InsertTailList (&mHotkeyList, &HotkeyRight->Link);
607 }
608
609 return EFI_SUCCESS;
610 }
611
612 /**
613
614 Process all the "Key####" variables, associate Hotkeys with corresponding Boot Options.
615
616 @retval EFI_SUCCESS Hotkey services successfully initialized.
617 @retval EFI_NOT_FOUND Can not find the "KeyOrder" variable
618 **/
619 EFI_STATUS
620 InitializeHotkeyService (
621 VOID
622 )
623 {
624 EFI_STATUS Status;
625 UINT32 BootOptionSupport;
626 UINT16 *KeyOrder;
627 UINTN KeyOrderSize;
628 UINTN Index;
629 UINT16 KeyOptionName[8];
630 UINTN KeyOptionSize;
631 EFI_KEY_OPTION *KeyOption;
632
633 //
634 // Export our capability - EFI_BOOT_OPTION_SUPPORT_KEY and EFI_BOOT_OPTION_SUPPORT_APP
635 //
636 BootOptionSupport = EFI_BOOT_OPTION_SUPPORT_KEY | EFI_BOOT_OPTION_SUPPORT_APP;
637 Status = gRT->SetVariable (
638 L"BootOptionSupport",
639 &gEfiGlobalVariableGuid,
640 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
641 sizeof (UINT32),
642 &BootOptionSupport
643 );
644
645 //
646 // Get valid Key Option List from private EFI variable "KeyOrder"
647 //
648 KeyOrder = BdsLibGetVariableAndSize (
649 VAR_KEY_ORDER,
650 &gEfiGlobalVariableGuid,
651 &KeyOrderSize
652 );
653
654 if (KeyOrder == NULL) {
655 return EFI_NOT_FOUND;
656 }
657
658 for (Index = 0; Index < KeyOrderSize / sizeof (UINT16); Index ++) {
659 UnicodeSPrint (KeyOptionName, sizeof (KeyOptionName), L"Key%04x", KeyOrder[Index]);
660 KeyOption = BdsLibGetVariableAndSize (
661 KeyOptionName,
662 &gEfiGlobalVariableGuid,
663 &KeyOptionSize
664 );
665
666 if (KeyOption == NULL || !IsKeyOptionValid (KeyOption)) {
667 UnregisterHotkey (KeyOrder[Index]);
668 } else {
669 HotkeyInsertList (KeyOption);
670 }
671 }
672
673 //
674 // Register Protocol notify for Hotkey service
675 //
676 Status = gBS->CreateEvent (
677 EVT_NOTIFY_SIGNAL,
678 TPL_CALLBACK,
679 HotkeyEvent,
680 NULL,
681 &mHotkeyEvent
682 );
683 ASSERT_EFI_ERROR (Status);
684
685 //
686 // Register for protocol notifications on this event
687 //
688 Status = gBS->RegisterProtocolNotify (
689 &gEfiSimpleTextInputExProtocolGuid,
690 mHotkeyEvent,
691 &mHotkeyRegistration
692 );
693 ASSERT_EFI_ERROR (Status);
694
695 return Status;
696 }
697