]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/Library/QemuFwCfgLib/QemuFwCfgLibMmio.c
NetworkPkg: Apply uncrustify changes
[mirror_edk2.git] / OvmfPkg / Library / QemuFwCfgLib / QemuFwCfgLibMmio.c
CommitLineData
6e2543b0
LE
1/** @file\r
2\r
3 Stateful and implicitly initialized fw_cfg library implementation.\r
4\r
5 Copyright (C) 2013 - 2014, Red Hat, Inc.\r
6 Copyright (c) 2011 - 2013, Intel Corporation. All rights reserved.<BR>\r
26aa241d 7 (C) Copyright 2021 Hewlett Packard Enterprise Development LP<BR>\r
6e2543b0 8\r
9792fb0e 9 SPDX-License-Identifier: BSD-2-Clause-Patent\r
6e2543b0
LE
10**/\r
11\r
7b6745cc
AB
12#include <Uefi.h>\r
13\r
6e2543b0
LE
14#include <Library/BaseLib.h>\r
15#include <Library/BaseMemoryLib.h>\r
953bcbcc 16#include <Library/DebugLib.h>\r
6e2543b0 17#include <Library/IoLib.h>\r
6e2543b0 18#include <Library/QemuFwCfgLib.h>\r
7b6745cc
AB
19#include <Library/UefiBootServicesTableLib.h>\r
20\r
21#include <Protocol/FdtClient.h>\r
6e2543b0
LE
22\r
23STATIC UINTN mFwCfgSelectorAddress;\r
24STATIC UINTN mFwCfgDataAddress;\r
953bcbcc
LE
25STATIC UINTN mFwCfgDmaAddress;\r
26\r
27/**\r
28 Reads firmware configuration bytes into a buffer\r
29\r
30 @param[in] Size Size in bytes to read\r
31 @param[in] Buffer Buffer to store data into (OPTIONAL if Size is 0)\r
32\r
33**/\r
34typedef\r
35VOID (EFIAPI READ_BYTES_FUNCTION) (\r
36 IN UINTN Size,\r
37 IN VOID *Buffer OPTIONAL\r
38 );\r
39\r
e8ae381f
LE
40/**\r
41 Writes bytes from a buffer to firmware configuration\r
42\r
43 @param[in] Size Size in bytes to write\r
44 @param[in] Buffer Buffer to transfer data from (OPTIONAL if Size is 0)\r
45\r
46**/\r
47typedef\r
48VOID (EFIAPI WRITE_BYTES_FUNCTION) (\r
49 IN UINTN Size,\r
50 IN VOID *Buffer OPTIONAL\r
51 );\r
52\r
7fcb7354
LE
53/**\r
54 Skips bytes in firmware configuration\r
55\r
56 @param[in] Size Size in bytes to skip\r
57\r
58**/\r
59typedef\r
60VOID (EFIAPI SKIP_BYTES_FUNCTION) (\r
61 IN UINTN Size\r
62 );\r
63\r
953bcbcc
LE
64//\r
65// Forward declaration of the two implementations we have.\r
66//\r
67STATIC READ_BYTES_FUNCTION MmioReadBytes;\r
e8ae381f 68STATIC WRITE_BYTES_FUNCTION MmioWriteBytes;\r
7fcb7354 69STATIC SKIP_BYTES_FUNCTION MmioSkipBytes;\r
953bcbcc 70STATIC READ_BYTES_FUNCTION DmaReadBytes;\r
e8ae381f 71STATIC WRITE_BYTES_FUNCTION DmaWriteBytes;\r
7fcb7354 72STATIC SKIP_BYTES_FUNCTION DmaSkipBytes;\r
953bcbcc
LE
73\r
74//\r
e8ae381f 75// These correspond to the implementation we detect at runtime.\r
953bcbcc
LE
76//\r
77STATIC READ_BYTES_FUNCTION *InternalQemuFwCfgReadBytes = MmioReadBytes;\r
e8ae381f 78STATIC WRITE_BYTES_FUNCTION *InternalQemuFwCfgWriteBytes = MmioWriteBytes;\r
7fcb7354 79STATIC SKIP_BYTES_FUNCTION *InternalQemuFwCfgSkipBytes = MmioSkipBytes;\r
953bcbcc 80\r
6e2543b0 81\r
6e2543b0
LE
82/**\r
83 Returns a boolean indicating if the firmware configuration interface\r
84 is available or not.\r
85\r
86 This function may change fw_cfg state.\r
87\r
88 @retval TRUE The interface is available\r
89 @retval FALSE The interface is not available\r
90\r
91**/\r
92BOOLEAN\r
93EFIAPI\r
94QemuFwCfgIsAvailable (\r
95 VOID\r
96 )\r
97{\r
1cb33be9 98 return (BOOLEAN)(mFwCfgSelectorAddress != 0 && mFwCfgDataAddress != 0);\r
6e2543b0
LE
99}\r
100\r
101\r
102RETURN_STATUS\r
103EFIAPI\r
104QemuFwCfgInitialize (\r
105 VOID\r
106 )\r
107{\r
7b6745cc
AB
108 EFI_STATUS Status;\r
109 FDT_CLIENT_PROTOCOL *FdtClient;\r
110 CONST UINT64 *Reg;\r
cfc8d51c
AB
111 UINT32 RegSize;\r
112 UINTN AddressCells, SizeCells;\r
7b6745cc
AB
113 UINT64 FwCfgSelectorAddress;\r
114 UINT64 FwCfgSelectorSize;\r
115 UINT64 FwCfgDataAddress;\r
116 UINT64 FwCfgDataSize;\r
117 UINT64 FwCfgDmaAddress;\r
118 UINT64 FwCfgDmaSize;\r
119\r
120 Status = gBS->LocateProtocol (&gFdtClientProtocolGuid, NULL,\r
121 (VOID **)&FdtClient);\r
122 ASSERT_EFI_ERROR (Status);\r
123\r
124 Status = FdtClient->FindCompatibleNodeReg (FdtClient, "qemu,fw-cfg-mmio",\r
cfc8d51c
AB
125 (CONST VOID **)&Reg, &AddressCells, &SizeCells,\r
126 &RegSize);\r
7b6745cc 127 if (EFI_ERROR (Status)) {\r
47719926 128 DEBUG ((DEBUG_WARN,\r
7b6745cc
AB
129 "%a: No 'qemu,fw-cfg-mmio' compatible DT node found (Status == %r)\n",\r
130 __FUNCTION__, Status));\r
131 return EFI_SUCCESS;\r
132 }\r
133\r
cfc8d51c
AB
134 ASSERT (AddressCells == 2);\r
135 ASSERT (SizeCells == 2);\r
7b6745cc
AB
136 ASSERT (RegSize == 2 * sizeof (UINT64));\r
137\r
138 FwCfgDataAddress = SwapBytes64 (Reg[0]);\r
139 FwCfgDataSize = 8;\r
140 FwCfgSelectorAddress = FwCfgDataAddress + FwCfgDataSize;\r
141 FwCfgSelectorSize = 2;\r
142\r
143 //\r
144 // The following ASSERT()s express\r
145 //\r
146 // Address + Size - 1 <= MAX_UINTN\r
147 //\r
148 // for both registers, that is, that the last byte in each MMIO range is\r
149 // expressible as a MAX_UINTN. The form below is mathematically\r
150 // equivalent, and it also prevents any unsigned overflow before the\r
151 // comparison.\r
152 //\r
153 ASSERT (FwCfgSelectorAddress <= MAX_UINTN - FwCfgSelectorSize + 1);\r
154 ASSERT (FwCfgDataAddress <= MAX_UINTN - FwCfgDataSize + 1);\r
155\r
156 mFwCfgSelectorAddress = FwCfgSelectorAddress;\r
157 mFwCfgDataAddress = FwCfgDataAddress;\r
158\r
47719926 159 DEBUG ((DEBUG_INFO, "Found FwCfg @ 0x%Lx/0x%Lx\n", FwCfgSelectorAddress,\r
7b6745cc
AB
160 FwCfgDataAddress));\r
161\r
162 if (SwapBytes64 (Reg[1]) >= 0x18) {\r
163 FwCfgDmaAddress = FwCfgDataAddress + 0x10;\r
164 FwCfgDmaSize = 0x08;\r
165\r
166 //\r
167 // See explanation above.\r
168 //\r
169 ASSERT (FwCfgDmaAddress <= MAX_UINTN - FwCfgDmaSize + 1);\r
170\r
47719926 171 DEBUG ((DEBUG_INFO, "Found FwCfg DMA @ 0x%Lx\n", FwCfgDmaAddress));\r
7b6745cc
AB
172 } else {\r
173 FwCfgDmaAddress = 0;\r
174 }\r
6e2543b0 175\r
1cb33be9 176 if (QemuFwCfgIsAvailable ()) {\r
6e2543b0
LE
177 UINT32 Signature;\r
178\r
179 QemuFwCfgSelectItem (QemuFwCfgItemSignature);\r
180 Signature = QemuFwCfgRead32 ();\r
953bcbcc
LE
181 if (Signature == SIGNATURE_32 ('Q', 'E', 'M', 'U')) {\r
182 //\r
183 // For DMA support, we require the DTB to advertise the register, and the\r
184 // feature bitmap (which we read without DMA) to confirm the feature.\r
185 //\r
7b6745cc 186 if (FwCfgDmaAddress != 0) {\r
953bcbcc
LE
187 UINT32 Features;\r
188\r
189 QemuFwCfgSelectItem (QemuFwCfgItemInterfaceVersion);\r
190 Features = QemuFwCfgRead32 ();\r
d61a5f45 191 if ((Features & FW_CFG_F_DMA) != 0) {\r
7b6745cc 192 mFwCfgDmaAddress = FwCfgDmaAddress;\r
953bcbcc 193 InternalQemuFwCfgReadBytes = DmaReadBytes;\r
e8ae381f 194 InternalQemuFwCfgWriteBytes = DmaWriteBytes;\r
7fcb7354 195 InternalQemuFwCfgSkipBytes = DmaSkipBytes;\r
953bcbcc
LE
196 }\r
197 }\r
198 } else {\r
6e2543b0
LE
199 mFwCfgSelectorAddress = 0;\r
200 mFwCfgDataAddress = 0;\r
201 }\r
202 }\r
203 return RETURN_SUCCESS;\r
204}\r
205\r
206\r
207/**\r
208 Selects a firmware configuration item for reading.\r
209\r
210 Following this call, any data read from this item will start from the\r
211 beginning of the configuration item's data.\r
212\r
213 @param[in] QemuFwCfgItem Firmware Configuration item to read\r
214\r
215**/\r
216VOID\r
217EFIAPI\r
218QemuFwCfgSelectItem (\r
219 IN FIRMWARE_CONFIG_ITEM QemuFwCfgItem\r
220 )\r
221{\r
1cb33be9 222 if (QemuFwCfgIsAvailable ()) {\r
6e2543b0
LE
223 MmioWrite16 (mFwCfgSelectorAddress, SwapBytes16 ((UINT16)QemuFwCfgItem));\r
224 }\r
225}\r
226\r
227\r
228/**\r
953bcbcc 229 Slow READ_BYTES_FUNCTION.\r
6e2543b0
LE
230**/\r
231STATIC\r
232VOID\r
233EFIAPI\r
953bcbcc 234MmioReadBytes (\r
6e2543b0
LE
235 IN UINTN Size,\r
236 IN VOID *Buffer OPTIONAL\r
237 )\r
238{\r
239 UINTN Left;\r
240 UINT8 *Ptr;\r
241 UINT8 *End;\r
242\r
26aa241d 243#if defined(MDE_CPU_AARCH64) || defined(MDE_CPU_RISCV64)\r
6e2543b0
LE
244 Left = Size & 7;\r
245#else\r
246 Left = Size & 3;\r
247#endif\r
248\r
249 Size -= Left;\r
250 Ptr = Buffer;\r
251 End = Ptr + Size;\r
252\r
26aa241d 253#if defined(MDE_CPU_AARCH64) || defined(MDE_CPU_RISCV64)\r
6e2543b0
LE
254 while (Ptr < End) {\r
255 *(UINT64 *)Ptr = MmioRead64 (mFwCfgDataAddress);\r
256 Ptr += 8;\r
257 }\r
258 if (Left & 4) {\r
259 *(UINT32 *)Ptr = MmioRead32 (mFwCfgDataAddress);\r
260 Ptr += 4;\r
261 }\r
262#else\r
263 while (Ptr < End) {\r
264 *(UINT32 *)Ptr = MmioRead32 (mFwCfgDataAddress);\r
265 Ptr += 4;\r
266 }\r
267#endif\r
268\r
269 if (Left & 2) {\r
270 *(UINT16 *)Ptr = MmioRead16 (mFwCfgDataAddress);\r
271 Ptr += 2;\r
272 }\r
273 if (Left & 1) {\r
274 *Ptr = MmioRead8 (mFwCfgDataAddress);\r
275 }\r
276}\r
277\r
278\r
953bcbcc 279/**\r
4175356f
LE
280 Transfer an array of bytes, or skip a number of bytes, using the DMA\r
281 interface.\r
282\r
283 @param[in] Size Size in bytes to transfer or skip.\r
284\r
285 @param[in,out] Buffer Buffer to read data into or write data from. Ignored,\r
286 and may be NULL, if Size is zero, or Control is\r
287 FW_CFG_DMA_CTL_SKIP.\r
288\r
289 @param[in] Control One of the following:\r
290 FW_CFG_DMA_CTL_WRITE - write to fw_cfg from Buffer.\r
291 FW_CFG_DMA_CTL_READ - read from fw_cfg into Buffer.\r
292 FW_CFG_DMA_CTL_SKIP - skip bytes in fw_cfg.\r
953bcbcc
LE
293**/\r
294STATIC\r
295VOID\r
4175356f
LE
296DmaTransferBytes (\r
297 IN UINTN Size,\r
298 IN OUT VOID *Buffer OPTIONAL,\r
299 IN UINT32 Control\r
953bcbcc
LE
300 )\r
301{\r
302 volatile FW_CFG_DMA_ACCESS Access;\r
303 UINT32 Status;\r
304\r
4175356f
LE
305 ASSERT (Control == FW_CFG_DMA_CTL_WRITE || Control == FW_CFG_DMA_CTL_READ ||\r
306 Control == FW_CFG_DMA_CTL_SKIP);\r
307\r
953bcbcc
LE
308 if (Size == 0) {\r
309 return;\r
310 }\r
311\r
312 ASSERT (Size <= MAX_UINT32);\r
313\r
4175356f 314 Access.Control = SwapBytes32 (Control);\r
953bcbcc
LE
315 Access.Length = SwapBytes32 ((UINT32)Size);\r
316 Access.Address = SwapBytes64 ((UINT64)(UINTN)Buffer);\r
317\r
318 //\r
319 // We shouldn't start the transfer before setting up Access.\r
320 //\r
321 MemoryFence ();\r
322\r
323 //\r
324 // This will fire off the transfer.\r
325 //\r
26aa241d 326#if defined(MDE_CPU_AARCH64) || defined(MDE_CPU_RISCV64)\r
953bcbcc
LE
327 MmioWrite64 (mFwCfgDmaAddress, SwapBytes64 ((UINT64)&Access));\r
328#else\r
329 MmioWrite32 ((UINT32)(mFwCfgDmaAddress + 4), SwapBytes32 ((UINT32)&Access));\r
330#endif\r
331\r
332 //\r
333 // We shouldn't look at Access.Control before starting the transfer.\r
334 //\r
335 MemoryFence ();\r
336\r
337 do {\r
338 Status = SwapBytes32 (Access.Control);\r
339 ASSERT ((Status & FW_CFG_DMA_CTL_ERROR) == 0);\r
340 } while (Status != 0);\r
341\r
342 //\r
343 // The caller will want to access the transferred data.\r
344 //\r
345 MemoryFence ();\r
346}\r
347\r
348\r
4175356f
LE
349/**\r
350 Fast READ_BYTES_FUNCTION.\r
351**/\r
352STATIC\r
353VOID\r
354EFIAPI\r
355DmaReadBytes (\r
356 IN UINTN Size,\r
357 IN VOID *Buffer OPTIONAL\r
358 )\r
359{\r
360 DmaTransferBytes (Size, Buffer, FW_CFG_DMA_CTL_READ);\r
361}\r
362\r
363\r
6e2543b0
LE
364/**\r
365 Reads firmware configuration bytes into a buffer\r
366\r
367 If called multiple times, then the data read will continue at the offset of\r
368 the firmware configuration item where the previous read ended.\r
369\r
370 @param[in] Size Size in bytes to read\r
371 @param[in] Buffer Buffer to store data into\r
372\r
373**/\r
374VOID\r
375EFIAPI\r
376QemuFwCfgReadBytes (\r
377 IN UINTN Size,\r
378 IN VOID *Buffer\r
379 )\r
380{\r
1cb33be9 381 if (QemuFwCfgIsAvailable ()) {\r
6e2543b0
LE
382 InternalQemuFwCfgReadBytes (Size, Buffer);\r
383 } else {\r
384 ZeroMem (Buffer, Size);\r
385 }\r
386}\r
387\r
e8ae381f
LE
388\r
389/**\r
390 Slow WRITE_BYTES_FUNCTION.\r
391**/\r
392STATIC\r
393VOID\r
394EFIAPI\r
395MmioWriteBytes (\r
396 IN UINTN Size,\r
397 IN VOID *Buffer OPTIONAL\r
398 )\r
399{\r
400 UINTN Idx;\r
401\r
402 for (Idx = 0; Idx < Size; ++Idx) {\r
403 MmioWrite8 (mFwCfgDataAddress, ((UINT8 *)Buffer)[Idx]);\r
404 }\r
405}\r
406\r
407\r
408/**\r
409 Fast WRITE_BYTES_FUNCTION.\r
410**/\r
411STATIC\r
412VOID\r
413EFIAPI\r
414DmaWriteBytes (\r
415 IN UINTN Size,\r
416 IN VOID *Buffer OPTIONAL\r
417 )\r
418{\r
419 DmaTransferBytes (Size, Buffer, FW_CFG_DMA_CTL_WRITE);\r
420}\r
421\r
422\r
6e2543b0
LE
423/**\r
424 Write firmware configuration bytes from a buffer\r
425\r
426 If called multiple times, then the data written will continue at the offset\r
427 of the firmware configuration item where the previous write ended.\r
428\r
429 @param[in] Size Size in bytes to write\r
430 @param[in] Buffer Buffer to read data from\r
431\r
432**/\r
433VOID\r
434EFIAPI\r
435QemuFwCfgWriteBytes (\r
436 IN UINTN Size,\r
437 IN VOID *Buffer\r
438 )\r
439{\r
1cb33be9 440 if (QemuFwCfgIsAvailable ()) {\r
e8ae381f 441 InternalQemuFwCfgWriteBytes (Size, Buffer);\r
6e2543b0
LE
442 }\r
443}\r
444\r
445\r
7fcb7354
LE
446/**\r
447 Slow SKIP_BYTES_FUNCTION.\r
448**/\r
449STATIC\r
450VOID\r
451EFIAPI\r
452MmioSkipBytes (\r
453 IN UINTN Size\r
454 )\r
455{\r
456 UINTN ChunkSize;\r
457 UINT8 SkipBuffer[256];\r
458\r
459 //\r
460 // Emulate the skip by reading data in chunks, and throwing it away. The\r
461 // implementation below doesn't affect the static data footprint for client\r
462 // modules. Large skips are not expected, therefore this fallback is not\r
463 // performance critical. The size of SkipBuffer is thought not to exert a\r
464 // large pressure on the stack.\r
465 //\r
466 while (Size > 0) {\r
467 ChunkSize = MIN (Size, sizeof SkipBuffer);\r
468 MmioReadBytes (ChunkSize, SkipBuffer);\r
469 Size -= ChunkSize;\r
470 }\r
471}\r
472\r
473\r
474/**\r
475 Fast SKIP_BYTES_FUNCTION.\r
476**/\r
477STATIC\r
478VOID\r
479EFIAPI\r
480DmaSkipBytes (\r
481 IN UINTN Size\r
482 )\r
483{\r
484 DmaTransferBytes (Size, NULL, FW_CFG_DMA_CTL_SKIP);\r
485}\r
486\r
487\r
488/**\r
489 Skip bytes in the firmware configuration item.\r
490\r
491 Increase the offset of the firmware configuration item without transferring\r
492 bytes between the item and a caller-provided buffer. Subsequent read, write\r
493 or skip operations will commence at the increased offset.\r
494\r
495 @param[in] Size Number of bytes to skip.\r
496**/\r
497VOID\r
498EFIAPI\r
499QemuFwCfgSkipBytes (\r
500 IN UINTN Size\r
501 )\r
502{\r
503 if (QemuFwCfgIsAvailable ()) {\r
504 InternalQemuFwCfgSkipBytes (Size);\r
505 }\r
506}\r
507\r
508\r
6e2543b0
LE
509/**\r
510 Reads a UINT8 firmware configuration value\r
511\r
512 @return Value of Firmware Configuration item read\r
513\r
514**/\r
515UINT8\r
516EFIAPI\r
517QemuFwCfgRead8 (\r
518 VOID\r
519 )\r
520{\r
521 UINT8 Result;\r
522\r
523 QemuFwCfgReadBytes (sizeof Result, &Result);\r
524 return Result;\r
525}\r
526\r
527\r
528/**\r
529 Reads a UINT16 firmware configuration value\r
530\r
531 @return Value of Firmware Configuration item read\r
532\r
533**/\r
534UINT16\r
535EFIAPI\r
536QemuFwCfgRead16 (\r
537 VOID\r
538 )\r
539{\r
540 UINT16 Result;\r
541\r
542 QemuFwCfgReadBytes (sizeof Result, &Result);\r
543 return Result;\r
544}\r
545\r
546\r
547/**\r
548 Reads a UINT32 firmware configuration value\r
549\r
550 @return Value of Firmware Configuration item read\r
551\r
552**/\r
553UINT32\r
554EFIAPI\r
555QemuFwCfgRead32 (\r
556 VOID\r
557 )\r
558{\r
559 UINT32 Result;\r
560\r
561 QemuFwCfgReadBytes (sizeof Result, &Result);\r
562 return Result;\r
563}\r
564\r
565\r
566/**\r
567 Reads a UINT64 firmware configuration value\r
568\r
569 @return Value of Firmware Configuration item read\r
570\r
571**/\r
572UINT64\r
573EFIAPI\r
574QemuFwCfgRead64 (\r
575 VOID\r
576 )\r
577{\r
578 UINT64 Result;\r
579\r
580 QemuFwCfgReadBytes (sizeof Result, &Result);\r
581 return Result;\r
582}\r
583\r
584\r
585/**\r
586 Find the configuration item corresponding to the firmware configuration file.\r
587\r
588 @param[in] Name Name of file to look up.\r
589 @param[out] Item Configuration item corresponding to the file, to be passed\r
590 to QemuFwCfgSelectItem ().\r
591 @param[out] Size Number of bytes in the file.\r
592\r
593 @retval RETURN_SUCCESS If file is found.\r
594 @retval RETURN_NOT_FOUND If file is not found.\r
595 @retval RETURN_UNSUPPORTED If firmware configuration is unavailable.\r
596\r
597**/\r
598RETURN_STATUS\r
599EFIAPI\r
600QemuFwCfgFindFile (\r
601 IN CONST CHAR8 *Name,\r
602 OUT FIRMWARE_CONFIG_ITEM *Item,\r
603 OUT UINTN *Size\r
604 )\r
605{\r
606 UINT32 Count;\r
607 UINT32 Idx;\r
608\r
1cb33be9 609 if (!QemuFwCfgIsAvailable ()) {\r
6e2543b0
LE
610 return RETURN_UNSUPPORTED;\r
611 }\r
612\r
613 QemuFwCfgSelectItem (QemuFwCfgItemFileDir);\r
614 Count = SwapBytes32 (QemuFwCfgRead32 ());\r
615\r
616 for (Idx = 0; Idx < Count; ++Idx) {\r
617 UINT32 FileSize;\r
618 UINT16 FileSelect;\r
619 CHAR8 FName[QEMU_FW_CFG_FNAME_SIZE];\r
620\r
621 FileSize = QemuFwCfgRead32 ();\r
622 FileSelect = QemuFwCfgRead16 ();\r
623 QemuFwCfgRead16 (); // skip the field called "reserved"\r
624 InternalQemuFwCfgReadBytes (sizeof (FName), FName);\r
625\r
626 if (AsciiStrCmp (Name, FName) == 0) {\r
3f318fbf 627 *Item = (FIRMWARE_CONFIG_ITEM) SwapBytes16 (FileSelect);\r
6e2543b0
LE
628 *Size = SwapBytes32 (FileSize);\r
629 return RETURN_SUCCESS;\r
630 }\r
631 }\r
632\r
633 return RETURN_NOT_FOUND;\r
634}\r