]> git.proxmox.com Git - mirror_edk2.git/blame_incremental - MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassBot.c
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[mirror_edk2.git] / MdeModulePkg / Bus / Usb / UsbMassStorageDxe / UsbMassBot.c
... / ...
CommitLineData
1/** @file\r
2 Implementation of the USB mass storage Bulk-Only Transport protocol,\r
3 according to USB Mass Storage Class Bulk-Only Transport, Revision 1.0.\r
4\r
5Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>\r
6SPDX-License-Identifier: BSD-2-Clause-Patent\r
7\r
8**/\r
9\r
10#include "UsbMass.h"\r
11\r
12//\r
13// Definition of USB BOT Transport Protocol\r
14//\r
15USB_MASS_TRANSPORT mUsbBotTransport = {\r
16 USB_MASS_STORE_BOT,\r
17 UsbBotInit,\r
18 UsbBotExecCommand,\r
19 UsbBotResetDevice,\r
20 UsbBotGetMaxLun,\r
21 UsbBotCleanUp\r
22};\r
23\r
24/**\r
25 Initializes USB BOT protocol.\r
26\r
27 This function initializes the USB mass storage class BOT protocol.\r
28 It will save its context which is a USB_BOT_PROTOCOL structure\r
29 in the Context if Context isn't NULL.\r
30\r
31 @param UsbIo The USB I/O Protocol instance\r
32 @param Context The buffer to save the context to\r
33\r
34 @retval EFI_SUCCESS The device is successfully initialized.\r
35 @retval EFI_UNSUPPORTED The transport protocol doesn't support the device.\r
36 @retval Other The USB BOT initialization fails.\r
37\r
38**/\r
39EFI_STATUS\r
40UsbBotInit (\r
41 IN EFI_USB_IO_PROTOCOL *UsbIo,\r
42 OUT VOID **Context OPTIONAL\r
43 )\r
44{\r
45 USB_BOT_PROTOCOL *UsbBot;\r
46 EFI_USB_INTERFACE_DESCRIPTOR *Interface;\r
47 EFI_USB_ENDPOINT_DESCRIPTOR EndPoint;\r
48 EFI_STATUS Status;\r
49 UINT8 Index;\r
50\r
51 //\r
52 // Allocate the BOT context for USB_BOT_PROTOCOL and two endpoint descriptors.\r
53 //\r
54 UsbBot = AllocateZeroPool (sizeof (USB_BOT_PROTOCOL) + 2 * sizeof (EFI_USB_ENDPOINT_DESCRIPTOR));\r
55 ASSERT (UsbBot != NULL);\r
56\r
57 UsbBot->UsbIo = UsbIo;\r
58\r
59 //\r
60 // Get the interface descriptor and validate that it\r
61 // is a USB Mass Storage BOT interface.\r
62 //\r
63 Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &UsbBot->Interface);\r
64\r
65 if (EFI_ERROR (Status)) {\r
66 goto ON_ERROR;\r
67 }\r
68\r
69 Interface = &UsbBot->Interface;\r
70\r
71 if (Interface->InterfaceProtocol != USB_MASS_STORE_BOT) {\r
72 Status = EFI_UNSUPPORTED;\r
73 goto ON_ERROR;\r
74 }\r
75\r
76 //\r
77 // Locate and save the first bulk-in and bulk-out endpoint\r
78 //\r
79 for (Index = 0; Index < Interface->NumEndpoints; Index++) {\r
80 Status = UsbIo->UsbGetEndpointDescriptor (UsbIo, Index, &EndPoint);\r
81\r
82 if (EFI_ERROR (Status) || !USB_IS_BULK_ENDPOINT (EndPoint.Attributes)) {\r
83 continue;\r
84 }\r
85\r
86 if (USB_IS_IN_ENDPOINT (EndPoint.EndpointAddress) &&\r
87 (UsbBot->BulkInEndpoint == NULL))\r
88 {\r
89 UsbBot->BulkInEndpoint = (EFI_USB_ENDPOINT_DESCRIPTOR *)(UsbBot + 1);\r
90 CopyMem (UsbBot->BulkInEndpoint, &EndPoint, sizeof (EndPoint));\r
91 }\r
92\r
93 if (USB_IS_OUT_ENDPOINT (EndPoint.EndpointAddress) &&\r
94 (UsbBot->BulkOutEndpoint == NULL))\r
95 {\r
96 UsbBot->BulkOutEndpoint = (EFI_USB_ENDPOINT_DESCRIPTOR *)(UsbBot + 1) + 1;\r
97 CopyMem (UsbBot->BulkOutEndpoint, &EndPoint, sizeof (EndPoint));\r
98 }\r
99 }\r
100\r
101 //\r
102 // If bulk-in or bulk-out endpoint is not found, report error.\r
103 //\r
104 if ((UsbBot->BulkInEndpoint == NULL) || (UsbBot->BulkOutEndpoint == NULL)) {\r
105 Status = EFI_UNSUPPORTED;\r
106 goto ON_ERROR;\r
107 }\r
108\r
109 //\r
110 // The USB BOT protocol uses CBWTag to match the CBW and CSW.\r
111 //\r
112 UsbBot->CbwTag = 0x01;\r
113\r
114 if (Context != NULL) {\r
115 *Context = UsbBot;\r
116 } else {\r
117 FreePool (UsbBot);\r
118 }\r
119\r
120 return EFI_SUCCESS;\r
121\r
122ON_ERROR:\r
123 FreePool (UsbBot);\r
124 return Status;\r
125}\r
126\r
127/**\r
128 Send the command to the device using Bulk-Out endpoint.\r
129\r
130 This function sends the command to the device using Bulk-Out endpoint.\r
131 BOT transfer is composed of three phases: Command, Data, and Status.\r
132 This is the Command phase.\r
133\r
134 @param UsbBot The USB BOT device\r
135 @param Cmd The command to transfer to device\r
136 @param CmdLen The length of the command\r
137 @param DataDir The direction of the data\r
138 @param TransLen The expected length of the data\r
139 @param Lun The number of logic unit\r
140\r
141 @retval EFI_SUCCESS The command is sent to the device.\r
142 @retval EFI_NOT_READY The device return NAK to the transfer\r
143 @retval Others Failed to send the command to device\r
144\r
145**/\r
146EFI_STATUS\r
147UsbBotSendCommand (\r
148 IN USB_BOT_PROTOCOL *UsbBot,\r
149 IN UINT8 *Cmd,\r
150 IN UINT8 CmdLen,\r
151 IN EFI_USB_DATA_DIRECTION DataDir,\r
152 IN UINT32 TransLen,\r
153 IN UINT8 Lun\r
154 )\r
155{\r
156 USB_BOT_CBW Cbw;\r
157 EFI_STATUS Status;\r
158 UINT32 Result;\r
159 UINTN DataLen;\r
160 UINTN Timeout;\r
161\r
162 ASSERT ((CmdLen > 0) && (CmdLen <= USB_BOT_MAX_CMDLEN));\r
163\r
164 //\r
165 // Fill in the Command Block Wrapper.\r
166 //\r
167 Cbw.Signature = USB_BOT_CBW_SIGNATURE;\r
168 Cbw.Tag = UsbBot->CbwTag;\r
169 Cbw.DataLen = TransLen;\r
170 Cbw.Flag = (UINT8)((DataDir == EfiUsbDataIn) ? BIT7 : 0);\r
171 Cbw.Lun = Lun;\r
172 Cbw.CmdLen = CmdLen;\r
173\r
174 ZeroMem (Cbw.CmdBlock, USB_BOT_MAX_CMDLEN);\r
175 CopyMem (Cbw.CmdBlock, Cmd, CmdLen);\r
176\r
177 Result = 0;\r
178 DataLen = sizeof (USB_BOT_CBW);\r
179 Timeout = USB_BOT_SEND_CBW_TIMEOUT / USB_MASS_1_MILLISECOND;\r
180\r
181 //\r
182 // Use USB I/O Protocol to send the Command Block Wrapper to the device.\r
183 //\r
184 Status = UsbBot->UsbIo->UsbBulkTransfer (\r
185 UsbBot->UsbIo,\r
186 UsbBot->BulkOutEndpoint->EndpointAddress,\r
187 &Cbw,\r
188 &DataLen,\r
189 Timeout,\r
190 &Result\r
191 );\r
192 if (EFI_ERROR (Status)) {\r
193 if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL) && (DataDir == EfiUsbDataOut)) {\r
194 //\r
195 // Respond to Bulk-Out endpoint stall with a Reset Recovery,\r
196 // according to section 5.3.1 of USB Mass Storage Class Bulk-Only Transport Spec, v1.0.\r
197 //\r
198 UsbBotResetDevice (UsbBot, FALSE);\r
199 } else if (USB_IS_ERROR (Result, EFI_USB_ERR_NAK)) {\r
200 Status = EFI_NOT_READY;\r
201 }\r
202 }\r
203\r
204 return Status;\r
205}\r
206\r
207/**\r
208 Transfer the data between the device and host.\r
209\r
210 This function transfers the data between the device and host.\r
211 BOT transfer is composed of three phases: Command, Data, and Status.\r
212 This is the Data phase.\r
213\r
214 @param UsbBot The USB BOT device\r
215 @param DataDir The direction of the data\r
216 @param Data The buffer to hold data\r
217 @param TransLen The expected length of the data\r
218 @param Timeout The time to wait the command to complete\r
219\r
220 @retval EFI_SUCCESS The data is transferred\r
221 @retval EFI_SUCCESS No data to transfer\r
222 @retval EFI_NOT_READY The device return NAK to the transfer\r
223 @retval Others Failed to transfer data\r
224\r
225**/\r
226EFI_STATUS\r
227UsbBotDataTransfer (\r
228 IN USB_BOT_PROTOCOL *UsbBot,\r
229 IN EFI_USB_DATA_DIRECTION DataDir,\r
230 IN OUT UINT8 *Data,\r
231 IN OUT UINTN *TransLen,\r
232 IN UINT32 Timeout\r
233 )\r
234{\r
235 EFI_USB_ENDPOINT_DESCRIPTOR *Endpoint;\r
236 EFI_STATUS Status;\r
237 UINT32 Result;\r
238\r
239 //\r
240 // If no data to transfer, just return EFI_SUCCESS.\r
241 //\r
242 if ((DataDir == EfiUsbNoData) || (*TransLen == 0)) {\r
243 return EFI_SUCCESS;\r
244 }\r
245\r
246 //\r
247 // Select the endpoint then issue the transfer\r
248 //\r
249 if (DataDir == EfiUsbDataIn) {\r
250 Endpoint = UsbBot->BulkInEndpoint;\r
251 } else {\r
252 Endpoint = UsbBot->BulkOutEndpoint;\r
253 }\r
254\r
255 Result = 0;\r
256 Timeout = Timeout / USB_MASS_1_MILLISECOND;\r
257\r
258 Status = UsbBot->UsbIo->UsbBulkTransfer (\r
259 UsbBot->UsbIo,\r
260 Endpoint->EndpointAddress,\r
261 Data,\r
262 TransLen,\r
263 Timeout,\r
264 &Result\r
265 );\r
266 if (EFI_ERROR (Status)) {\r
267 if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL)) {\r
268 DEBUG ((DEBUG_INFO, "UsbBotDataTransfer: (%r)\n", Status));\r
269 DEBUG ((DEBUG_INFO, "UsbBotDataTransfer: DataIn Stall\n"));\r
270 UsbClearEndpointStall (UsbBot->UsbIo, Endpoint->EndpointAddress);\r
271 } else if (USB_IS_ERROR (Result, EFI_USB_ERR_NAK)) {\r
272 Status = EFI_NOT_READY;\r
273 } else {\r
274 DEBUG ((DEBUG_ERROR, "UsbBotDataTransfer: (%r)\n", Status));\r
275 }\r
276\r
277 if (Status == EFI_TIMEOUT) {\r
278 UsbBotResetDevice (UsbBot, FALSE);\r
279 }\r
280 }\r
281\r
282 return Status;\r
283}\r
284\r
285/**\r
286 Get the command execution status from device.\r
287\r
288 This function gets the command execution status from device.\r
289 BOT transfer is composed of three phases: Command, Data, and Status.\r
290 This is the Status phase.\r
291\r
292 This function returns the transfer status of the BOT's CSW status,\r
293 and returns the high level command execution result in Result. So\r
294 even if EFI_SUCCESS is returned, the command may still have failed.\r
295\r
296 @param UsbBot The USB BOT device.\r
297 @param TransLen The expected length of the data.\r
298 @param CmdStatus The result of the command execution.\r
299\r
300 @retval EFI_SUCCESS Command execute result is retrieved and in the Result.\r
301 @retval Other Error occurred when trying to get status.\r
302\r
303**/\r
304EFI_STATUS\r
305UsbBotGetStatus (\r
306 IN USB_BOT_PROTOCOL *UsbBot,\r
307 IN UINT32 TransLen,\r
308 OUT UINT8 *CmdStatus\r
309 )\r
310{\r
311 USB_BOT_CSW Csw;\r
312 UINTN Len;\r
313 UINT8 Endpoint;\r
314 EFI_STATUS Status;\r
315 UINT32 Result;\r
316 EFI_USB_IO_PROTOCOL *UsbIo;\r
317 UINT32 Index;\r
318 UINTN Timeout;\r
319\r
320 *CmdStatus = USB_BOT_COMMAND_ERROR;\r
321 Status = EFI_DEVICE_ERROR;\r
322 Endpoint = UsbBot->BulkInEndpoint->EndpointAddress;\r
323 UsbIo = UsbBot->UsbIo;\r
324 Timeout = USB_BOT_RECV_CSW_TIMEOUT / USB_MASS_1_MILLISECOND;\r
325\r
326 for (Index = 0; Index < USB_BOT_RECV_CSW_RETRY; Index++) {\r
327 //\r
328 // Attempt to the read Command Status Wrapper from bulk in endpoint\r
329 //\r
330 ZeroMem (&Csw, sizeof (USB_BOT_CSW));\r
331 Result = 0;\r
332 Len = sizeof (USB_BOT_CSW);\r
333 Status = UsbIo->UsbBulkTransfer (\r
334 UsbIo,\r
335 Endpoint,\r
336 &Csw,\r
337 &Len,\r
338 Timeout,\r
339 &Result\r
340 );\r
341 if (EFI_ERROR (Status)) {\r
342 if (USB_IS_ERROR (Result, EFI_USB_ERR_STALL)) {\r
343 UsbClearEndpointStall (UsbIo, Endpoint);\r
344 }\r
345\r
346 continue;\r
347 }\r
348\r
349 if (Csw.Signature != USB_BOT_CSW_SIGNATURE) {\r
350 //\r
351 // CSW is invalid, so perform reset recovery\r
352 //\r
353 Status = UsbBotResetDevice (UsbBot, FALSE);\r
354 } else if (Csw.CmdStatus == USB_BOT_COMMAND_ERROR) {\r
355 //\r
356 // Respond phase error also needs reset recovery\r
357 //\r
358 Status = UsbBotResetDevice (UsbBot, FALSE);\r
359 } else {\r
360 *CmdStatus = Csw.CmdStatus;\r
361 break;\r
362 }\r
363 }\r
364\r
365 //\r
366 // The tag is increased even if there is an error.\r
367 //\r
368 UsbBot->CbwTag++;\r
369\r
370 return Status;\r
371}\r
372\r
373/**\r
374 Call the USB Mass Storage Class BOT protocol to issue\r
375 the command/data/status circle to execute the commands.\r
376\r
377 @param Context The context of the BOT protocol, that is,\r
378 USB_BOT_PROTOCOL\r
379 @param Cmd The high level command\r
380 @param CmdLen The command length\r
381 @param DataDir The direction of the data transfer\r
382 @param Data The buffer to hold data\r
383 @param DataLen The length of the data\r
384 @param Lun The number of logic unit\r
385 @param Timeout The time to wait command\r
386 @param CmdStatus The result of high level command execution\r
387\r
388 @retval EFI_SUCCESS The command is executed successfully.\r
389 @retval Other Failed to execute command\r
390\r
391**/\r
392EFI_STATUS\r
393UsbBotExecCommand (\r
394 IN VOID *Context,\r
395 IN VOID *Cmd,\r
396 IN UINT8 CmdLen,\r
397 IN EFI_USB_DATA_DIRECTION DataDir,\r
398 IN VOID *Data,\r
399 IN UINT32 DataLen,\r
400 IN UINT8 Lun,\r
401 IN UINT32 Timeout,\r
402 OUT UINT32 *CmdStatus\r
403 )\r
404{\r
405 USB_BOT_PROTOCOL *UsbBot;\r
406 EFI_STATUS Status;\r
407 UINTN TransLen;\r
408 UINT8 Result;\r
409\r
410 *CmdStatus = USB_MASS_CMD_FAIL;\r
411 UsbBot = (USB_BOT_PROTOCOL *)Context;\r
412\r
413 //\r
414 // Send the command to the device. Return immediately if device\r
415 // rejects the command.\r
416 //\r
417 Status = UsbBotSendCommand (UsbBot, Cmd, CmdLen, DataDir, DataLen, Lun);\r
418 if (EFI_ERROR (Status)) {\r
419 DEBUG ((DEBUG_ERROR, "UsbBotExecCommand: UsbBotSendCommand (%r)\n", Status));\r
420 return Status;\r
421 }\r
422\r
423 //\r
424 // Transfer the data. Don't return immediately even data transfer\r
425 // failed. The host should attempt to receive the CSW no matter\r
426 // whether it succeeds or fails.\r
427 //\r
428 TransLen = (UINTN)DataLen;\r
429 UsbBotDataTransfer (UsbBot, DataDir, Data, &TransLen, Timeout);\r
430\r
431 //\r
432 // Get the status, if that succeeds, interpret the result\r
433 //\r
434 Status = UsbBotGetStatus (UsbBot, DataLen, &Result);\r
435 if (EFI_ERROR (Status)) {\r
436 DEBUG ((DEBUG_ERROR, "UsbBotExecCommand: UsbBotGetStatus (%r)\n", Status));\r
437 return Status;\r
438 }\r
439\r
440 if (Result == 0) {\r
441 *CmdStatus = USB_MASS_CMD_SUCCESS;\r
442 }\r
443\r
444 return EFI_SUCCESS;\r
445}\r
446\r
447/**\r
448 Reset the USB mass storage device by BOT protocol.\r
449\r
450 @param Context The context of the BOT protocol, that is,\r
451 USB_BOT_PROTOCOL.\r
452 @param ExtendedVerification If FALSE, just issue Bulk-Only Mass Storage Reset request.\r
453 If TRUE, additionally reset parent hub port.\r
454\r
455 @retval EFI_SUCCESS The device is reset.\r
456 @retval Others Failed to reset the device..\r
457\r
458**/\r
459EFI_STATUS\r
460UsbBotResetDevice (\r
461 IN VOID *Context,\r
462 IN BOOLEAN ExtendedVerification\r
463 )\r
464{\r
465 USB_BOT_PROTOCOL *UsbBot;\r
466 EFI_USB_DEVICE_REQUEST Request;\r
467 EFI_STATUS Status;\r
468 UINT32 Result;\r
469 UINT32 Timeout;\r
470\r
471 UsbBot = (USB_BOT_PROTOCOL *)Context;\r
472\r
473 if (ExtendedVerification) {\r
474 //\r
475 // If we need to do strictly reset, reset its parent hub port\r
476 //\r
477 Status = UsbBot->UsbIo->UsbPortReset (UsbBot->UsbIo);\r
478 if (EFI_ERROR (Status)) {\r
479 return EFI_DEVICE_ERROR;\r
480 }\r
481 }\r
482\r
483 //\r
484 // Issue a class specific Bulk-Only Mass Storage Reset request,\r
485 // according to section 3.1 of USB Mass Storage Class Bulk-Only Transport Spec, v1.0.\r
486 //\r
487 Request.RequestType = 0x21;\r
488 Request.Request = USB_BOT_RESET_REQUEST;\r
489 Request.Value = 0;\r
490 Request.Index = UsbBot->Interface.InterfaceNumber;\r
491 Request.Length = 0;\r
492 Timeout = USB_BOT_RESET_DEVICE_TIMEOUT / USB_MASS_1_MILLISECOND;\r
493\r
494 Status = UsbBot->UsbIo->UsbControlTransfer (\r
495 UsbBot->UsbIo,\r
496 &Request,\r
497 EfiUsbNoData,\r
498 Timeout,\r
499 NULL,\r
500 0,\r
501 &Result\r
502 );\r
503\r
504 if (EFI_ERROR (Status)) {\r
505 return EFI_DEVICE_ERROR;\r
506 }\r
507\r
508 //\r
509 // The device shall NAK the host's request until the reset is\r
510 // complete. We can use this to sync the device and host. For\r
511 // now just stall 100ms to wait for the device.\r
512 //\r
513 gBS->Stall (USB_BOT_RESET_DEVICE_STALL);\r
514\r
515 //\r
516 // Clear the Bulk-In and Bulk-Out stall condition.\r
517 //\r
518 UsbClearEndpointStall (UsbBot->UsbIo, UsbBot->BulkInEndpoint->EndpointAddress);\r
519 UsbClearEndpointStall (UsbBot->UsbIo, UsbBot->BulkOutEndpoint->EndpointAddress);\r
520\r
521 return Status;\r
522}\r
523\r
524/**\r
525 Get the max LUN (Logical Unit Number) of USB mass storage device.\r
526\r
527 @param Context The context of the BOT protocol, that is, USB_BOT_PROTOCOL\r
528 @param MaxLun Return pointer to the max number of LUN. (e.g. MaxLun=1 means LUN0 and\r
529 LUN1 in all.)\r
530\r
531 @retval EFI_SUCCESS Max LUN is got successfully.\r
532 @retval Others Fail to execute this request.\r
533\r
534**/\r
535EFI_STATUS\r
536UsbBotGetMaxLun (\r
537 IN VOID *Context,\r
538 OUT UINT8 *MaxLun\r
539 )\r
540{\r
541 USB_BOT_PROTOCOL *UsbBot;\r
542 EFI_USB_DEVICE_REQUEST Request;\r
543 EFI_STATUS Status;\r
544 UINT32 Result;\r
545 UINT32 Timeout;\r
546\r
547 if ((Context == NULL) || (MaxLun == NULL)) {\r
548 return EFI_INVALID_PARAMETER;\r
549 }\r
550\r
551 UsbBot = (USB_BOT_PROTOCOL *)Context;\r
552\r
553 //\r
554 // Issue a class specific Bulk-Only Mass Storage get max lun request.\r
555 // according to section 3.2 of USB Mass Storage Class Bulk-Only Transport Spec, v1.0.\r
556 //\r
557 Request.RequestType = 0xA1;\r
558 Request.Request = USB_BOT_GETLUN_REQUEST;\r
559 Request.Value = 0;\r
560 Request.Index = UsbBot->Interface.InterfaceNumber;\r
561 Request.Length = 1;\r
562 Timeout = USB_BOT_RESET_DEVICE_TIMEOUT / USB_MASS_1_MILLISECOND;\r
563\r
564 Status = UsbBot->UsbIo->UsbControlTransfer (\r
565 UsbBot->UsbIo,\r
566 &Request,\r
567 EfiUsbDataIn,\r
568 Timeout,\r
569 (VOID *)MaxLun,\r
570 1,\r
571 &Result\r
572 );\r
573 if (EFI_ERROR (Status) || (*MaxLun > USB_BOT_MAX_LUN)) {\r
574 //\r
575 // If the Get LUN request returns an error or the MaxLun is larger than\r
576 // the maximum LUN value (0x0f) supported by the USB Mass Storage Class\r
577 // Bulk-Only Transport Spec, then set MaxLun to 0.\r
578 //\r
579 // This improves compatibility with USB FLASH drives that have a single LUN\r
580 // and either do not return a max LUN value or return an invalid maximum LUN\r
581 // value.\r
582 //\r
583 *MaxLun = 0;\r
584 }\r
585\r
586 return EFI_SUCCESS;\r
587}\r
588\r
589/**\r
590 Clean up the resource used by this BOT protocol.\r
591\r
592 @param Context The context of the BOT protocol, that is, USB_BOT_PROTOCOL.\r
593\r
594 @retval EFI_SUCCESS The resource is cleaned up.\r
595\r
596**/\r
597EFI_STATUS\r
598UsbBotCleanUp (\r
599 IN VOID *Context\r
600 )\r
601{\r
602 FreePool (Context);\r
603 return EFI_SUCCESS;\r
604}\r