1 /** @file
2 #
3 # Copyright (c) 2014, ARM Ltd. All rights reserved.<BR>
4 #
5 # This program and the accompanying materials
6 # are licensed and made available under the terms and conditions of the BSD License
7 # which accompanies this distribution. The full text of the license may be found at
8 # http://opensource.org/licenses/bsd-license.php
9 # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
11 #
12 #
13 #**/
14
15 #include <Protocol/AndroidFastbootTransport.h>
16 #include <Protocol/Dhcp4.h>
17 #include <Protocol/Tcp4.h>
18 #include <Protocol/ServiceBinding.h>
19 #include <Protocol/SimpleTextOut.h>
20
21 #include <Library/BaseLib.h>
22 #include <Library/BaseMemoryLib.h>
23 #include <Library/DebugLib.h>
24 #include <Library/MemoryAllocationLib.h>
25 #include <Library/PrintLib.h>
26 #include <Library/UefiBootServicesTableLib.h>
27 #include <Library/UefiDriverEntryPoint.h>
28 #include <Library/UefiRuntimeServicesTableLib.h>
29
30 #define IP4_ADDR_TO_STRING(IpAddr, IpAddrString) UnicodeSPrint ( \
31 IpAddrString, \
32 16 * 2, \
33 L"%d.%d.%d.%d", \
34 IpAddr.Addr[0], \
35 IpAddr.Addr[1], \
36 IpAddr.Addr[2], \
37 IpAddr.Addr[3] \
38 );
39
40 // Fastboot says max packet size is 512, but FASTBOOT_TRANSPORT_PROTOCOL
41 // doesn't place a limit on the size of buffers returned by Receive.
42 // (This isn't actually a packet size - it's just the size of the buffers we
43 // pass to the TCP driver to fill with received data.)
44 // We can achieve much better performance by doing this in larger chunks.
45 #define RX_FRAGMENT_SIZE 2048
46
47 STATIC EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *mTextOut;
48
49 STATIC EFI_TCP4_PROTOCOL *mTcpConnection;
50 STATIC EFI_TCP4_PROTOCOL *mTcpListener;
51
52 STATIC EFI_EVENT mReceiveEvent;
53
54 STATIC EFI_SERVICE_BINDING_PROTOCOL *mTcpServiceBinding;
55 STATIC EFI_HANDLE mTcpHandle = NULL;
56
57 // We only ever use one IO token for receive and one for transmit. To save
58 // repeatedly allocating and freeing, just allocate statically and re-use.
59 #define NUM_RX_TOKENS 16
60 #define TOKEN_NEXT(Index) (((Index) + 1) % NUM_RX_TOKENS)
61
62 STATIC UINTN mNextSubmitIndex;
63 STATIC UINTN mNextReceiveIndex;
64 STATIC EFI_TCP4_IO_TOKEN mReceiveToken[NUM_RX_TOKENS];
65 STATIC EFI_TCP4_RECEIVE_DATA mRxData[NUM_RX_TOKENS];
66 STATIC EFI_TCP4_IO_TOKEN mTransmitToken;
67 STATIC EFI_TCP4_TRANSMIT_DATA mTxData;
68 // We also reuse the accept token
69 STATIC EFI_TCP4_LISTEN_TOKEN mAcceptToken;
70 // .. and the close token
71 STATIC EFI_TCP4_CLOSE_TOKEN mCloseToken;
72
73 // List type for queued received packets
74 typedef struct _FASTBOOT_TCP_PACKET_LIST {
75 LIST_ENTRY Link;
76 VOID *Buffer;
77 UINTN BufferSize;
78 } FASTBOOT_TCP_PACKET_LIST;
79
80 STATIC LIST_ENTRY mPacketListHead;
81
82 STATIC
83 VOID
84 EFIAPI
85 DataReceived (
86 IN EFI_EVENT Event,
87 IN VOID *Context
88 );
89
90 /*
91 Helper function to set up a receive IO token and call Tcp->Receive
92 */
93 STATIC
94 EFI_STATUS
SubmitRecieveToken(VOID)95 SubmitRecieveToken (
96 VOID
97 )
98 {
99 EFI_STATUS Status;
100 VOID *FragmentBuffer;
101
102 Status = EFI_SUCCESS;
103
104 FragmentBuffer = AllocatePool (RX_FRAGMENT_SIZE);
105 ASSERT (FragmentBuffer != NULL);
106 if (FragmentBuffer == NULL) {
107 DEBUG ((EFI_D_ERROR, "TCP Fastboot out of resources"));
108 return EFI_OUT_OF_RESOURCES;
109 }
110
111 mRxData[mNextSubmitIndex].DataLength = RX_FRAGMENT_SIZE;
112 mRxData[mNextSubmitIndex].FragmentTable[0].FragmentLength = RX_FRAGMENT_SIZE;
113 mRxData[mNextSubmitIndex].FragmentTable[0].FragmentBuffer = FragmentBuffer;
114
115 Status = mTcpConnection->Receive (mTcpConnection, &mReceiveToken[mNextSubmitIndex]);
116 if (EFI_ERROR (Status)) {
117 DEBUG ((EFI_D_ERROR, "TCP Receive: %r\n", Status));
118 FreePool (FragmentBuffer);
119 }
120
121 mNextSubmitIndex = TOKEN_NEXT (mNextSubmitIndex);
122 return Status;
123 }
124
125 /*
126 Event notify function for when we have closed our TCP connection.
127 We can now start listening for another connection.
128 */
129 STATIC
130 VOID
ConnectionClosed(IN EFI_EVENT Event,IN VOID * Context)131 ConnectionClosed (
132 IN EFI_EVENT Event,
133 IN VOID *Context
134 )
135 {
136 EFI_STATUS Status;
137
138 // Possible bug in EDK2 TCP4 driver: closing a connection doesn't remove its
139 // PCB from the list of live connections. Subsequent attempts to Configure()
140 // a TCP instance with the same local port will fail with INVALID_PARAMETER.
141 // Calling Configure with NULL is a workaround for this issue.
142 Status = mTcpConnection->Configure (mTcpConnection, NULL);
143
144 mTcpConnection = NULL;
145
146 Status = mTcpListener->Accept (mTcpListener, &mAcceptToken);
147 if (EFI_ERROR (Status)) {
148 DEBUG ((EFI_D_ERROR, "TCP Accept: %r\n", Status));
149 }
150 }
151
152 STATIC
153 VOID
CloseReceiveEvents(VOID)154 CloseReceiveEvents (
155 VOID
156 )
157 {
158 UINTN Index;
159
160 for (Index = 0; Index < NUM_RX_TOKENS; Index++) {
161 gBS->CloseEvent (mReceiveToken[Index].CompletionToken.Event);
162 }
163 }
164
165 /*
166 Event notify function to be called when we receive TCP data.
167 */
168 STATIC
169 VOID
170 EFIAPI
DataReceived(IN EFI_EVENT Event,IN VOID * Context)171 DataReceived (
172 IN EFI_EVENT Event,
173 IN VOID *Context
174 )
175 {
176 EFI_STATUS Status;
177 FASTBOOT_TCP_PACKET_LIST *NewEntry;
178 EFI_TCP4_IO_TOKEN *ReceiveToken;
179
180 ReceiveToken = &mReceiveToken[mNextReceiveIndex];
181
182 Status = ReceiveToken->CompletionToken.Status;
183
184 if (Status == EFI_CONNECTION_FIN) {
185 //
186 // Remote host closed connection. Close our end.
187 //
188
189 CloseReceiveEvents ();
190
191 Status = mTcpConnection->Close (mTcpConnection, &mCloseToken);
192 ASSERT_EFI_ERROR (Status);
193
194 return;
195 }
196
197 //
198 // Add an element to the receive queue
199 //
200
201 NewEntry = AllocatePool (sizeof (FASTBOOT_TCP_PACKET_LIST));
202 if (NewEntry == NULL) {
203 DEBUG ((EFI_D_ERROR, "TCP Fastboot: Out of resources\n"));
204 return;
205 }
206
207 mNextReceiveIndex = TOKEN_NEXT (mNextReceiveIndex);
208
209 if (!EFI_ERROR (Status)) {
210 NewEntry->Buffer
211 = ReceiveToken->Packet.RxData->FragmentTable[0].FragmentBuffer;
212 NewEntry->BufferSize
213 = ReceiveToken->Packet.RxData->FragmentTable[0].FragmentLength;
214
215 // Prepare to receive more data
216 SubmitRecieveToken();
217 } else {
218 // Fatal receive error. Put an entry with NULL in the queue, signifying
219 // to return EFI_DEVICE_ERROR from TcpFastbootTransportReceive.
220 NewEntry->Buffer = NULL;
221 NewEntry->BufferSize = 0;
222
223 DEBUG ((EFI_D_ERROR, "\nTCP Fastboot Receive error: %r\n", Status));
224 }
225
226 InsertTailList (&mPacketListHead, &NewEntry->Link);
227
228 Status = gBS->SignalEvent (mReceiveEvent);
229 ASSERT_EFI_ERROR (Status);
230 }
231
232
233 /*
234 Event notify function to be called when we accept an incoming TCP connection.
235 */
236 STATIC
237 VOID
238 EFIAPI
ConnectionAccepted(IN EFI_EVENT Event,IN VOID * Context)239 ConnectionAccepted (
240 IN EFI_EVENT Event,
241 IN VOID *Context
242 )
243 {
244 EFI_TCP4_LISTEN_TOKEN *AcceptToken;
245 EFI_STATUS Status;
246 UINTN Index;
247
248 AcceptToken = (EFI_TCP4_LISTEN_TOKEN *) Context;
249 Status = AcceptToken->CompletionToken.Status;
250
251 if (EFI_ERROR (Status)) {
252 DEBUG ((EFI_D_ERROR, "TCP Fastboot: Connection Error: %r\n", Status));
253 return;
254 }
255 DEBUG ((EFI_D_ERROR, "TCP Fastboot: Connection Received.\n"));
256
257 //
258 // Accepting a new TCP connection creates a new instance of the TCP protocol.
259 // Open it and prepare to receive on it.
260 //
261
262 Status = gBS->OpenProtocol (
263 AcceptToken->NewChildHandle,
264 &gEfiTcp4ProtocolGuid,
265 (VOID **) &mTcpConnection,
266 gImageHandle,
267 NULL,
268 EFI_OPEN_PROTOCOL_GET_PROTOCOL
269 );
270 if (EFI_ERROR (Status)) {
271 DEBUG ((EFI_D_ERROR, "Open TCP Connection: %r\n", Status));
272 return;
273 }
274
275 mNextSubmitIndex = 0;
276 mNextReceiveIndex = 0;
277
278 for (Index = 0; Index < NUM_RX_TOKENS; Index++) {
279 Status = gBS->CreateEvent (
280 EVT_NOTIFY_SIGNAL,
281 TPL_CALLBACK,
282 DataReceived,
283 NULL,
284 &(mReceiveToken[Index].CompletionToken.Event)
285 );
286 ASSERT_EFI_ERROR (Status);
287 }
288
289 for (Index = 0; Index < NUM_RX_TOKENS; Index++) {
290 SubmitRecieveToken();
291 }
292 }
293
294 /*
295 Set up TCP Fastboot transport: Configure the network device via DHCP then
296 start waiting for a TCP connection on the Fastboot port.
297 */
298 EFI_STATUS
TcpFastbootTransportStart(EFI_EVENT ReceiveEvent)299 TcpFastbootTransportStart (
300 EFI_EVENT ReceiveEvent
301 )
302 {
303 EFI_STATUS Status;
304 EFI_HANDLE NetDeviceHandle;
305 EFI_HANDLE *HandleBuffer;
306 EFI_IP4_MODE_DATA Ip4ModeData;
307 UINTN NumHandles;
308 CHAR16 IpAddrString[16];
309 UINTN Index;
310
311 EFI_TCP4_CONFIG_DATA TcpConfigData = {
312 0x00, // IPv4 Type of Service
313 255, // IPv4 Time to Live
314 { // AccessPoint:
315 TRUE, // Use default address
316 { {0, 0, 0, 0} }, // IP Address (ignored - use default)
317 { {0, 0, 0, 0} }, // Subnet mask (ignored - use default)
318 FixedPcdGet32 (PcdAndroidFastbootTcpPort), // Station port
319 { {0, 0, 0, 0} }, // Remote address: accept any
320 0, // Remote Port: accept any
321 FALSE // ActiveFlag: be a "server"
322 },
323 NULL // Default advanced TCP options
324 };
325
326 mReceiveEvent = ReceiveEvent;
327 InitializeListHead (&mPacketListHead);
328
329 mTextOut->OutputString (mTextOut, L"Initialising TCP Fastboot transport...\r\n");
330
331 //
332 // Open a passive TCP instance
333 //
334
335 Status = gBS->LocateHandleBuffer (
336 ByProtocol,
337 &gEfiTcp4ServiceBindingProtocolGuid,
338 NULL,
339 &NumHandles,
340 &HandleBuffer
341 );
342 if (EFI_ERROR (Status)) {
343 DEBUG ((EFI_D_ERROR, "Find TCP Service Binding: %r\n", Status));
344 return Status;
345 }
346
347 // We just use the first network device
348 NetDeviceHandle = HandleBuffer[0];
349
350 Status = gBS->OpenProtocol (
351 NetDeviceHandle,
352 &gEfiTcp4ServiceBindingProtocolGuid,
353 (VOID **) &mTcpServiceBinding,
354 gImageHandle,
355 NULL,
356 EFI_OPEN_PROTOCOL_GET_PROTOCOL
357 );
358 if (EFI_ERROR (Status)) {
359 DEBUG ((EFI_D_ERROR, "Open TCP Service Binding: %r\n", Status));
360 return Status;
361 }
362
363 Status = mTcpServiceBinding->CreateChild (mTcpServiceBinding, &mTcpHandle);
364 if (EFI_ERROR (Status)) {
365 DEBUG ((EFI_D_ERROR, "TCP ServiceBinding Create: %r\n", Status));
366 return Status;
367 }
368
369 Status = gBS->OpenProtocol (
370 mTcpHandle,
371 &gEfiTcp4ProtocolGuid,
372 (VOID **) &mTcpListener,
373 gImageHandle,
374 NULL,
375 EFI_OPEN_PROTOCOL_GET_PROTOCOL
376 );
377 if (EFI_ERROR (Status)) {
378 DEBUG ((EFI_D_ERROR, "Open TCP Protocol: %r\n", Status));
379 }
380
381 //
382 // Set up re-usable tokens
383 //
384
385 for (Index = 0; Index < NUM_RX_TOKENS; Index++) {
386 mRxData[Index].UrgentFlag = FALSE;
387 mRxData[Index].FragmentCount = 1;
388 mReceiveToken[Index].Packet.RxData = &mRxData[Index];
389 }
390
391 mTxData.Push = TRUE;
392 mTxData.Urgent = FALSE;
393 mTxData.FragmentCount = 1;
394 mTransmitToken.Packet.TxData = &mTxData;
395
396 Status = gBS->CreateEvent (
397 EVT_NOTIFY_SIGNAL,
398 TPL_CALLBACK,
399 ConnectionAccepted,
400 &mAcceptToken,
401 &mAcceptToken.CompletionToken.Event
402 );
403 ASSERT_EFI_ERROR (Status);
404
405 Status = gBS->CreateEvent (
406 EVT_NOTIFY_SIGNAL,
407 TPL_CALLBACK,
408 ConnectionClosed,
409 &mCloseToken,
410 &mCloseToken.CompletionToken.Event
411 );
412 ASSERT_EFI_ERROR (Status);
413
414 //
415 // Configure the TCP instance
416 //
417
418 Status = mTcpListener->Configure (mTcpListener, &TcpConfigData);
419 if (Status == EFI_NO_MAPPING) {
420 // Wait until the IP configuration process (probably DHCP) has finished
421 do {
422 Status = mTcpListener->GetModeData (mTcpListener,
423 NULL, NULL,
424 &Ip4ModeData,
425 NULL, NULL
426 );
427 ASSERT_EFI_ERROR (Status);
428 } while (!Ip4ModeData.IsConfigured);
429 Status = mTcpListener->Configure (mTcpListener, &TcpConfigData);
430 } else if (EFI_ERROR (Status)) {
431 DEBUG ((EFI_D_ERROR, "TCP Configure: %r\n", Status));
432 return Status;
433 }
434
435 //
436 // Tell the user our address and hostname
437 //
438 IP4_ADDR_TO_STRING (Ip4ModeData.ConfigData.StationAddress, IpAddrString);
439
440 mTextOut->OutputString (mTextOut, L"TCP Fastboot transport configured.");
441 mTextOut->OutputString (mTextOut, L"\r\nIP address: ");
442 mTextOut->OutputString (mTextOut ,IpAddrString);
443 mTextOut->OutputString (mTextOut, L"\r\n");
444
445 //
446 // Start listening for a connection
447 //
448
449 Status = mTcpListener->Accept (mTcpListener, &mAcceptToken);
450 if (EFI_ERROR (Status)) {
451 DEBUG ((EFI_D_ERROR, "TCP Accept: %r\n", Status));
452 return Status;
453 }
454
455 mTextOut->OutputString (mTextOut, L"TCP Fastboot transport initialised.\r\n");
456
457 FreePool (HandleBuffer);
458
459 return EFI_SUCCESS;
460 }
461
462 EFI_STATUS
TcpFastbootTransportStop(VOID)463 TcpFastbootTransportStop (
464 VOID
465 )
466 {
467 EFI_TCP4_CLOSE_TOKEN CloseToken;
468 EFI_STATUS Status;
469 UINTN EventIndex;
470 FASTBOOT_TCP_PACKET_LIST *Entry;
471 FASTBOOT_TCP_PACKET_LIST *NextEntry;
472
473 // Close any existing TCP connection, blocking until it's done.
474 if (mTcpConnection != NULL) {
475 CloseReceiveEvents ();
476
477 CloseToken.AbortOnClose = FALSE;
478
479 Status = gBS->CreateEvent (0, 0, NULL, NULL, &CloseToken.CompletionToken.Event);
480 ASSERT_EFI_ERROR (Status);
481
482 Status = mTcpConnection->Close (mTcpConnection, &CloseToken);
483 ASSERT_EFI_ERROR (Status);
484
485 Status = gBS->WaitForEvent (
486 1,
487 &CloseToken.CompletionToken.Event,
488 &EventIndex
489 );
490 ASSERT_EFI_ERROR (Status);
491
492 ASSERT_EFI_ERROR (CloseToken.CompletionToken.Status);
493
494 // Possible bug in EDK2 TCP4 driver: closing a connection doesn't remove its
495 // PCB from the list of live connections. Subsequent attempts to Configure()
496 // a TCP instance with the same local port will fail with INVALID_PARAMETER.
497 // Calling Configure with NULL is a workaround for this issue.
498 Status = mTcpConnection->Configure (mTcpConnection, NULL);
499 ASSERT_EFI_ERROR (Status);
500 }
501
502
503 gBS->CloseEvent (mAcceptToken.CompletionToken.Event);
504
505 // Stop listening for connections.
506 // Ideally we would do this with Cancel, but it isn't implemented by EDK2.
507 // So we just "reset this TCPv4 instance brutally".
508 Status = mTcpListener->Configure (mTcpListener, NULL);
509 ASSERT_EFI_ERROR (Status);
510
511 Status = mTcpServiceBinding->DestroyChild (mTcpServiceBinding, &mTcpHandle);
512
513 // Free any data the user didn't pick up
514 Entry = (FASTBOOT_TCP_PACKET_LIST *) GetFirstNode (&mPacketListHead);
515 while (!IsNull (&mPacketListHead, &Entry->Link)) {
516 NextEntry = (FASTBOOT_TCP_PACKET_LIST *) GetNextNode (&mPacketListHead, &Entry->Link);
517
518 RemoveEntryList (&Entry->Link);
519 if (Entry->Buffer) {
520 FreePool (Entry->Buffer);
521 }
522 FreePool (Entry);
523
524 Entry = NextEntry;
525 }
526
527 return EFI_SUCCESS;
528 }
529
530 /*
531 Event notify function for when data has been sent. Free resources and report
532 errors.
533 Context should point to the transmit IO token passed to
534 TcpConnection->Transmit.
535 */
536 STATIC
537 VOID
DataSent(EFI_EVENT Event,VOID * Context)538 DataSent (
539 EFI_EVENT Event,
540 VOID *Context
541 )
542 {
543 EFI_STATUS Status;
544
545 Status = mTransmitToken.CompletionToken.Status;
546 if (EFI_ERROR (Status)) {
547 DEBUG ((EFI_D_ERROR, "TCP Fastboot transmit result: %r\n", Status));
548 gBS->SignalEvent (*(EFI_EVENT *) Context);
549 }
550
551 FreePool (mTransmitToken.Packet.TxData->FragmentTable[0].FragmentBuffer);
552 }
553
554 EFI_STATUS
TcpFastbootTransportSend(IN UINTN BufferSize,IN CONST VOID * Buffer,IN EFI_EVENT * FatalErrorEvent)555 TcpFastbootTransportSend (
556 IN UINTN BufferSize,
557 IN CONST VOID *Buffer,
558 IN EFI_EVENT *FatalErrorEvent
559 )
560 {
561 EFI_STATUS Status;
562
563 if (BufferSize > 512) {
564 return EFI_INVALID_PARAMETER;
565 }
566
567 //
568 // Build transmit IO token
569 //
570
571 // Create an event so we are notified when a transmission is complete.
572 // We use this to free resources and report errors.
573 Status = gBS->CreateEvent (
574 EVT_NOTIFY_SIGNAL,
575 TPL_CALLBACK,
576 DataSent,
577 FatalErrorEvent,
578 &mTransmitToken.CompletionToken.Event
579 );
580 ASSERT_EFI_ERROR (Status);
581
582 mTxData.DataLength = BufferSize;
583
584 mTxData.FragmentTable[0].FragmentLength = BufferSize;
585 mTxData.FragmentTable[0].FragmentBuffer = AllocateCopyPool (
586 BufferSize,
587 Buffer
588 );
589
590 Status = mTcpConnection->Transmit (mTcpConnection, &mTransmitToken);
591 if (EFI_ERROR (Status)) {
592 DEBUG ((EFI_D_ERROR, "TCP Transmit: %r\n", Status));
593 return Status;
594 }
595
596 return EFI_SUCCESS;
597 }
598
599
600 EFI_STATUS
TcpFastbootTransportReceive(OUT UINTN * BufferSize,OUT VOID ** Buffer)601 TcpFastbootTransportReceive (
602 OUT UINTN *BufferSize,
603 OUT VOID **Buffer
604 )
605 {
606 FASTBOOT_TCP_PACKET_LIST *Entry;
607
608 if (IsListEmpty (&mPacketListHead)) {
609 return EFI_NOT_READY;
610 }
611
612 Entry = (FASTBOOT_TCP_PACKET_LIST *) GetFirstNode (&mPacketListHead);
613
614 if (Entry->Buffer == NULL) {
615 // There was an error receiving this packet.
616 return EFI_DEVICE_ERROR;
617 }
618
619 *Buffer = Entry->Buffer;
620 *BufferSize = Entry->BufferSize;
621
622 RemoveEntryList (&Entry->Link);
623 FreePool (Entry);
624
625 return EFI_SUCCESS;
626 }
627
628 FASTBOOT_TRANSPORT_PROTOCOL mTransportProtocol = {
629 TcpFastbootTransportStart,
630 TcpFastbootTransportStop,
631 TcpFastbootTransportSend,
632 TcpFastbootTransportReceive
633 };
634
635 EFI_STATUS
TcpFastbootTransportEntryPoint(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)636 TcpFastbootTransportEntryPoint (
637 IN EFI_HANDLE ImageHandle,
638 IN EFI_SYSTEM_TABLE *SystemTable
639 )
640 {
641 EFI_STATUS Status;
642
643
644 Status = gBS->LocateProtocol(
645 &gEfiSimpleTextOutProtocolGuid,
646 NULL,
647 (VOID **) &mTextOut
648 );
649 if (EFI_ERROR (Status)) {
650 DEBUG ((EFI_D_ERROR, "Fastboot: Open Text Output Protocol: %r\n", Status));
651 return Status;
652 }
653
654 Status = gBS->InstallProtocolInterface (
655 &ImageHandle,
656 &gAndroidFastbootTransportProtocolGuid,
657 EFI_NATIVE_INTERFACE,
658 &mTransportProtocol
659 );
660 if (EFI_ERROR (Status)) {
661 DEBUG ((EFI_D_ERROR, "Fastboot: Install transport Protocol: %r\n", Status));
662 }
663
664 return Status;
665 }
666