LPCOpen Platform
LPCOpen Platform for NXP LPC Microcontrollers
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
MassStorageClassHost.c
Go to the documentation of this file.
1 /*
2  * @brief Host mode driver for the library USB Mass Storage Class driver
3  *
4  * @note
5  * Copyright(C) NXP Semiconductors, 2012
6  * Copyright(C) Dean Camera, 2011, 2012
7  * All rights reserved.
8  *
9  * @par
10  * Software that is described herein is for illustrative purposes only
11  * which provides customers with programming information regarding the
12  * LPC products. This software is supplied "AS IS" without any warranties of
13  * any kind, and NXP Semiconductors and its licensor disclaim any and
14  * all warranties, express or implied, including all implied warranties of
15  * merchantability, fitness for a particular purpose and non-infringement of
16  * intellectual property rights. NXP Semiconductors assumes no responsibility
17  * or liability for the use of the software, conveys no license or rights under any
18  * patent, copyright, mask work right, or any other intellectual property rights in
19  * or to any products. NXP Semiconductors reserves the right to make changes
20  * in the software without notification. NXP Semiconductors also makes no
21  * representation or warranty that such application will be suitable for the
22  * specified use without further testing or modification.
23  *
24  * @par
25  * Permission to use, copy, modify, and distribute this software and its
26  * documentation is hereby granted, under NXP Semiconductors' and its
27  * licensor's relevant copyrights in the software, without fee, provided that it
28  * is used in conjunction with NXP Semiconductors microcontrollers. This
29  * copyright, permission, and disclaimer notice must appear in all copies of
30  * this code.
31  */
32 
33 
34 #define __INCLUDE_FROM_USB_DRIVER
35 #include "../../Core/USBMode.h"
36 
37 #if defined(USB_CAN_BE_HOST)
38 
39 #define __INCLUDE_FROM_MS_DRIVER
40 #define __INCLUDE_FROM_MASSSTORAGE_HOST_C
41 #include "MassStorageClassHost.h"
42 
43 uint8_t MS_Host_ConfigurePipes(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
44  uint16_t ConfigDescriptorSize,
45  void* ConfigDescriptorData)
46 {
47  USB_Descriptor_Endpoint_t* DataINEndpoint = NULL;
48  USB_Descriptor_Endpoint_t* DataOUTEndpoint = NULL;
49  USB_Descriptor_Interface_t* MassStorageInterface = NULL;
50  uint8_t portnum = MSInterfaceInfo->Config.PortNumber;
51 
52  memset(&MSInterfaceInfo->State, 0x00, sizeof(MSInterfaceInfo->State));
53 
54  if (DESCRIPTOR_TYPE(ConfigDescriptorData) != DTYPE_Configuration)
56 
57  while (!(DataINEndpoint) || !(DataOUTEndpoint))
58  {
59  if (!(MassStorageInterface) ||
60  USB_GetNextDescriptorComp(&ConfigDescriptorSize, &ConfigDescriptorData,
62  {
63  if (USB_GetNextDescriptorComp(&ConfigDescriptorSize, &ConfigDescriptorData,
65  {
67  }
68 
69  MassStorageInterface = DESCRIPTOR_PCAST(ConfigDescriptorData, USB_Descriptor_Interface_t);
70 
71  DataINEndpoint = NULL;
72  DataOUTEndpoint = NULL;
73 
74  continue;
75  }
76 
77  USB_Descriptor_Endpoint_t* EndpointData = DESCRIPTOR_PCAST(ConfigDescriptorData, USB_Descriptor_Endpoint_t);
78 
79  if ((EndpointData->EndpointAddress & ENDPOINT_DIR_MASK) == ENDPOINT_DIR_IN)
80  DataINEndpoint = EndpointData;
81  else
82  DataOUTEndpoint = EndpointData;
83  }
84 
85  for (uint8_t PipeNum = 1; PipeNum < PIPE_TOTAL_PIPES; PipeNum++)
86  {
87  uint16_t Size;
88  uint8_t Type;
89  uint8_t Token;
90  uint8_t EndpointAddress;
91  bool DoubleBanked;
92 
93  if (PipeNum == MSInterfaceInfo->Config.DataINPipeNumber)
94  {
95  Size = le16_to_cpu(DataINEndpoint->EndpointSize);
96  EndpointAddress = DataINEndpoint->EndpointAddress;
97  Token = PIPE_TOKEN_IN;
98  Type = EP_TYPE_BULK;
99  DoubleBanked = MSInterfaceInfo->Config.DataINPipeDoubleBank;
100 
101  MSInterfaceInfo->State.DataINPipeSize = DataINEndpoint->EndpointSize;
102  }
103  else if (PipeNum == MSInterfaceInfo->Config.DataOUTPipeNumber)
104  {
105  Size = le16_to_cpu(DataOUTEndpoint->EndpointSize);
106  EndpointAddress = DataOUTEndpoint->EndpointAddress;
107  Token = PIPE_TOKEN_OUT;
108  Type = EP_TYPE_BULK;
109  DoubleBanked = MSInterfaceInfo->Config.DataOUTPipeDoubleBank;
110 
111  MSInterfaceInfo->State.DataOUTPipeSize = DataOUTEndpoint->EndpointSize;
112  }
113  else
114  {
115  continue;
116  }
117 
118  if (!(Pipe_ConfigurePipe(portnum,PipeNum, Type, Token, EndpointAddress, Size,
119  DoubleBanked ? PIPE_BANK_DOUBLE : PIPE_BANK_SINGLE)))
120  {
122  }
123  }
124 
125  MSInterfaceInfo->State.InterfaceNumber = MassStorageInterface->InterfaceNumber;
126  MSInterfaceInfo->State.IsActive = true;
127 
128  return MS_ENUMERROR_NoError;
129 }
130 
131 static uint8_t DCOMP_MS_Host_NextMSInterface(void* const CurrentDescriptor)
132 {
134 
135  if (Header->Type == DTYPE_Interface)
136  {
138 
139  if ((Interface->Class == MS_CSCP_MassStorageClass) &&
140  (Interface->SubClass == MS_CSCP_SCSITransparentSubclass) &&
141  (Interface->Protocol == MS_CSCP_BulkOnlyTransportProtocol))
142  {
144  }
145  }
146 
148 }
149 
150 static uint8_t DCOMP_MS_Host_NextMSInterfaceEndpoint(void* const CurrentDescriptor)
151 {
153 
154  if (Header->Type == DTYPE_Endpoint)
155  {
157 
158  uint8_t EndpointType = (Endpoint->Attributes & EP_TYPE_MASK);
159 
160  if ((EndpointType == EP_TYPE_BULK) && (!(Pipe_IsEndpointBound(Endpoint->EndpointAddress))))
161  {
163  }
164  }
165  else if (Header->Type == DTYPE_Interface)
166  {
167  return DESCRIPTOR_SEARCH_Fail;
168  }
169 
171 }
172 
173 static uint8_t MS_Host_SendCommand(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
174  MS_CommandBlockWrapper_t* const SCSICommandBlock,
175  const void* const BufferPtr)
176 {
177  uint8_t ErrorCode = PIPE_RWSTREAM_NoError;
178  uint8_t portnum = MSInterfaceInfo->Config.PortNumber;
179 
180  if (++MSInterfaceInfo->State.TransactionTag == 0xFFFFFFFF)
181  MSInterfaceInfo->State.TransactionTag = 1;
182 
183  SCSICommandBlock->Signature = CPU_TO_LE32(MS_CBW_SIGNATURE);
184  SCSICommandBlock->Tag = cpu_to_le32(MSInterfaceInfo->State.TransactionTag);
185 
186  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataOUTPipeNumber);
187  Pipe_Unfreeze();
188 
189  if ((ErrorCode = Pipe_Write_Stream_LE(portnum,SCSICommandBlock, sizeof(MS_CommandBlockWrapper_t),
191  {
192  return ErrorCode;
193  }
194 
195  Pipe_ClearOUT(portnum);
196  Pipe_WaitUntilReady(portnum);
197 
198  Pipe_Freeze();
199 
200  if (BufferPtr != NULL)
201  {
202  ErrorCode = MS_Host_SendReceiveData(MSInterfaceInfo, SCSICommandBlock, (void*)BufferPtr);
203 
204  if ((ErrorCode != PIPE_RWSTREAM_NoError) && (ErrorCode != PIPE_RWSTREAM_PipeStalled))
205  {
206  Pipe_Freeze();
207  return ErrorCode;
208  }
209  }
210 
211  MS_CommandStatusWrapper_t SCSIStatusBlock;
212  return MS_Host_GetReturnedStatus(MSInterfaceInfo, &SCSIStatusBlock);
213 }
214 
215 static uint8_t MS_Host_WaitForDataReceived(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo)
216 {
217  uint16_t TimeoutMSRem = MS_COMMAND_DATA_TIMEOUT_MS;
218  uint16_t PreviousFrameNumber = USB_Host_GetFrameNumber();
219  uint8_t portnum = MSInterfaceInfo->Config.PortNumber;
220 
221  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataINPipeNumber);
222  Pipe_Unfreeze();
223 
224  while (!(Pipe_IsINReceived(portnum)))
225  {
226  uint16_t CurrentFrameNumber = USB_Host_GetFrameNumber();
227 
228  if (CurrentFrameNumber != PreviousFrameNumber)
229  {
230  PreviousFrameNumber = CurrentFrameNumber;
231 
232  if (!(TimeoutMSRem--))
233  return PIPE_RWSTREAM_Timeout;
234  }
235 
236  Pipe_Freeze();
237  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataOUTPipeNumber);
238  Pipe_Unfreeze();
239 
240  if (Pipe_IsStalled(portnum))
241  {
242  Pipe_ClearStall(portnum);
245  }
246 
247  Pipe_Freeze();
248  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataINPipeNumber);
249  Pipe_Unfreeze();
250 
251  if (Pipe_IsStalled(portnum))
252  {
253  Pipe_ClearStall(portnum);
256  }
257 
258  if (USB_HostState[portnum] == HOST_STATE_Unattached)
260  };
261 
262  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataINPipeNumber);
263  Pipe_Freeze();
264 
265  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataOUTPipeNumber);
266  Pipe_Freeze();
267 
268  return PIPE_RWSTREAM_NoError;
269 }
270 
271 static uint8_t MS_Host_SendReceiveData(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
272  MS_CommandBlockWrapper_t* const SCSICommandBlock,
273  void* BufferPtr)
274 {
275  uint8_t ErrorCode = PIPE_RWSTREAM_NoError;
276  uint16_t BytesRem = le32_to_cpu(SCSICommandBlock->DataTransferLength);
277  uint8_t portnum = MSInterfaceInfo->Config.PortNumber;
278 
279  if (SCSICommandBlock->Flags & MS_COMMAND_DIR_DATA_IN)
280  {
281  if ((ErrorCode = MS_Host_WaitForDataReceived(MSInterfaceInfo)) != PIPE_RWSTREAM_NoError)
282  {
283  Pipe_Freeze();
284  return ErrorCode;
285  }
286 
287  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataINPipeNumber);
288  Pipe_Unfreeze();
289 
290  if ((ErrorCode = Pipe_Read_Stream_LE(portnum,BufferPtr, BytesRem, NULL)) != PIPE_RWSTREAM_NoError)
291  return ErrorCode;
292 
293  Pipe_ClearIN(portnum);
294  }
295  else
296  {
297  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataOUTPipeNumber);
298  Pipe_Unfreeze();
299 
300  if ((ErrorCode = Pipe_Write_Stream_LE(portnum,BufferPtr, BytesRem, NULL)) != PIPE_RWSTREAM_NoError)
301  return ErrorCode;
302 
303  Pipe_ClearOUT(portnum);
304 
305  while (!(Pipe_IsOUTReady(portnum)))
306  {
307  if (USB_HostState[portnum] == HOST_STATE_Unattached)
309  }
310  }
311 
312  Pipe_Freeze();
313 
314  return ErrorCode;
315 }
316 
317 static uint8_t MS_Host_GetReturnedStatus(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
318  MS_CommandStatusWrapper_t* const SCSICommandStatus)
319 {
320  uint8_t ErrorCode = PIPE_RWSTREAM_NoError;
321  uint8_t portnum = MSInterfaceInfo->Config.PortNumber;
322 
323  if ((ErrorCode = MS_Host_WaitForDataReceived(MSInterfaceInfo)) != PIPE_RWSTREAM_NoError)
324  return ErrorCode;
325 
326  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataINPipeNumber);
327  Pipe_Unfreeze();
328 
329  if ((ErrorCode = Pipe_Read_Stream_LE(portnum,SCSICommandStatus, sizeof(MS_CommandStatusWrapper_t),
331  {
332  return ErrorCode;
333  }
334 
335  Pipe_ClearIN(portnum);
336  Pipe_Freeze();
337 
338  if (SCSICommandStatus->Status != MS_SCSI_COMMAND_Pass)
339  ErrorCode = MS_ERROR_LOGICAL_CMD_FAILED;
340 
341  return ErrorCode;
342 }
343 
344 uint8_t MS_Host_ResetMSInterface(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo)
345 {
346  uint8_t ErrorCode;
347  uint8_t portnum = MSInterfaceInfo->Config.PortNumber;
349  {
350  .bmRequestType = (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE),
352  .wValue = 0,
353  .wIndex = MSInterfaceInfo->State.InterfaceNumber,
354  .wLength = 0,
355  };
356 
358 
359  if ((ErrorCode = USB_Host_SendControlRequest(portnum,NULL)) != HOST_SENDCONTROL_Successful)
360  return ErrorCode;
361 
362  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataINPipeNumber);
363 
365  return ErrorCode;
366 
367  Pipe_SelectPipe(portnum,MSInterfaceInfo->Config.DataOUTPipeNumber);
368 
370  return ErrorCode;
371 
373 }
374 
375 uint8_t MS_Host_GetMaxLUN(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
376  uint8_t* const MaxLUNIndex)
377 {
378  uint8_t ErrorCode;
379  uint8_t portnum = MSInterfaceInfo->Config.PortNumber;
381  {
382  .bmRequestType = (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE),
384  .wValue = 0,
385  .wIndex = MSInterfaceInfo->State.InterfaceNumber,
386  .wLength = 1,
387  };
388 
390 
391  if ((ErrorCode = USB_Host_SendControlRequest(portnum,MaxLUNIndex)) == HOST_SENDCONTROL_SetupStalled)
392  {
393  *MaxLUNIndex = 0;
394  ErrorCode = HOST_SENDCONTROL_Successful;
395  }
396 
397  return ErrorCode;
398 }
399 
400 uint8_t MS_Host_GetInquiryData(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
401  const uint8_t LUNIndex,
403 {
404  uint8_t portnum = MSInterfaceInfo->Config.PortNumber;
405  uint8_t ErrorCode;
406  if ((USB_HostState[portnum] != HOST_STATE_Configured) || !(MSInterfaceInfo->State.IsActive))
408 
410  {
411  .DataTransferLength = CPU_TO_LE32(sizeof(SCSI_Inquiry_Response_t)),
412  .Flags = MS_COMMAND_DIR_DATA_IN,
413  .LUN = LUNIndex,
414  .SCSICommandLength = 6,
415  .SCSICommandData =
416  {
418  0x00, // Reserved
419  0x00, // Reserved
420  0x00, // Reserved
421  sizeof(SCSI_Inquiry_Response_t), // Allocation Length
422  0x00 // Unused (control)
423  }
424  };
425 
426  if ((ErrorCode = MS_Host_SendCommand(MSInterfaceInfo, &SCSICommandBlock, InquiryData)) != PIPE_RWSTREAM_NoError)
427  return ErrorCode;
428 
429  return PIPE_RWSTREAM_NoError;
430 }
431 
432 uint8_t MS_Host_TestUnitReady(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
433  const uint8_t LUNIndex)
434 {
435  if ((USB_HostState[MSInterfaceInfo->Config.PortNumber] != HOST_STATE_Configured) || !(MSInterfaceInfo->State.IsActive))
437 
438  uint8_t ErrorCode;
439 
441  {
442  .DataTransferLength = CPU_TO_LE32(0),
443  .Flags = MS_COMMAND_DIR_DATA_IN,
444  .LUN = LUNIndex,
445  .SCSICommandLength = 6,
446  .SCSICommandData =
447  {
449  0x00, // Reserved
450  0x00, // Reserved
451  0x00, // Reserved
452  0x00, // Reserved
453  0x00 // Unused (control)
454  }
455  };
456 
457  if ((ErrorCode = MS_Host_SendCommand(MSInterfaceInfo, &SCSICommandBlock, NULL)) != PIPE_RWSTREAM_NoError)
458  return ErrorCode;
459 
460  return PIPE_RWSTREAM_NoError;
461 }
462 
464  const uint8_t LUNIndex,
465  SCSI_Capacity_t* const DeviceCapacity)
466 {
467  if ((USB_HostState[MSInterfaceInfo->Config.PortNumber] != HOST_STATE_Configured) || !(MSInterfaceInfo->State.IsActive))
469 
470  uint8_t ErrorCode;
471 
473  {
474  .DataTransferLength = CPU_TO_LE32(sizeof(SCSI_Capacity_t)),
475  .Flags = MS_COMMAND_DIR_DATA_IN,
476  .LUN = LUNIndex,
477  .SCSICommandLength = 10,
478  .SCSICommandData =
479  {
481  0x00, // Reserved
482  0x00, // MSB of Logical block address
483  0x00,
484  0x00,
485  0x00, // LSB of Logical block address
486  0x00, // Reserved
487  0x00, // Reserved
488  0x00, // Partial Medium Indicator
489  0x00 // Unused (control)
490  }
491  };
492 
493  if ((ErrorCode = MS_Host_SendCommand(MSInterfaceInfo, &SCSICommandBlock, DeviceCapacity)) != PIPE_RWSTREAM_NoError)
494  return ErrorCode;
495 
496  DeviceCapacity->Blocks = BE32_TO_CPU(DeviceCapacity->Blocks);
497  DeviceCapacity->BlockSize = BE32_TO_CPU(DeviceCapacity->BlockSize);
498 
499  return PIPE_RWSTREAM_NoError;
500 }
501 
502 uint8_t MS_Host_RequestSense(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
503  const uint8_t LUNIndex,
505 {
506  if ((USB_HostState[MSInterfaceInfo->Config.PortNumber] != HOST_STATE_Configured) || !(MSInterfaceInfo->State.IsActive))
508 
509  uint8_t ErrorCode;
510 
512  {
513  .DataTransferLength = CPU_TO_LE32(sizeof(SCSI_Request_Sense_Response_t)),
514  .Flags = MS_COMMAND_DIR_DATA_IN,
515  .LUN = LUNIndex,
516  .SCSICommandLength = 6,
517  .SCSICommandData =
518  {
520  0x00, // Reserved
521  0x00, // Reserved
522  0x00, // Reserved
523  sizeof(SCSI_Request_Sense_Response_t), // Allocation Length
524  0x00 // Unused (control)
525  }
526  };
527 
528  if ((ErrorCode = MS_Host_SendCommand(MSInterfaceInfo, &SCSICommandBlock, SenseData)) != PIPE_RWSTREAM_NoError)
529  return ErrorCode;
530 
531  return PIPE_RWSTREAM_NoError;
532 }
533 
535  const uint8_t LUNIndex,
536  const bool PreventRemoval)
537 {
538  if ((USB_HostState[MSInterfaceInfo->Config.PortNumber] != HOST_STATE_Configured) || !(MSInterfaceInfo->State.IsActive))
540 
541  uint8_t ErrorCode;
542 
544  {
545  .DataTransferLength = CPU_TO_LE32(0),
546  .Flags = MS_COMMAND_DIR_DATA_OUT,
547  .LUN = LUNIndex,
548  .SCSICommandLength = 6,
549  .SCSICommandData =
550  {
552  0x00, // Reserved
553  0x00, // Reserved
554  PreventRemoval, // Prevent flag
555  0x00, // Reserved
556  0x00 // Unused (control)
557  }
558  };
559 
560  if ((ErrorCode = MS_Host_SendCommand(MSInterfaceInfo, &SCSICommandBlock, NULL)) != PIPE_RWSTREAM_NoError)
561  return ErrorCode;
562 
563  return PIPE_RWSTREAM_NoError;
564 }
565 
566 uint8_t MS_Host_ReadDeviceBlocks(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
567  const uint8_t LUNIndex,
568  const uint32_t BlockAddress,
569  const uint8_t Blocks,
570  const uint16_t BlockSize,
571  void* BlockBuffer)
572 {
573  if ((USB_HostState[MSInterfaceInfo->Config.PortNumber] != HOST_STATE_Configured) || !(MSInterfaceInfo->State.IsActive))
575 
576  uint8_t ErrorCode;
577 
579  {
580  .DataTransferLength = cpu_to_le32((uint32_t)Blocks * BlockSize),
581  .Flags = MS_COMMAND_DIR_DATA_IN,
582  .LUN = LUNIndex,
583  .SCSICommandLength = 10,
584  .SCSICommandData =
585  {
587  0x00, // Unused (control bits, all off)
588  (BlockAddress >> 24), // MSB of Block Address
589  (BlockAddress >> 16),
590  (BlockAddress >> 8),
591  (BlockAddress & 0xFF), // LSB of Block Address
592  0x00, // Reserved
593  0x00, // MSB of Total Blocks to Read
594  Blocks, // LSB of Total Blocks to Read
595  0x00 // Unused (control)
596  }
597  };
598 
599  if ((ErrorCode = MS_Host_SendCommand(MSInterfaceInfo, &SCSICommandBlock, BlockBuffer)) != PIPE_RWSTREAM_NoError)
600  return ErrorCode;
601 
602  return PIPE_RWSTREAM_NoError;
603 }
604 
605 uint8_t MS_Host_WriteDeviceBlocks(USB_ClassInfo_MS_Host_t* const MSInterfaceInfo,
606  const uint8_t LUNIndex,
607  const uint32_t BlockAddress,
608  const uint8_t Blocks,
609  const uint16_t BlockSize,
610  const void* BlockBuffer)
611 {
612  if ((USB_HostState[MSInterfaceInfo->Config.PortNumber] != HOST_STATE_Configured) || !(MSInterfaceInfo->State.IsActive))
614 
615  uint8_t ErrorCode;
616 
618  {
619  .DataTransferLength = cpu_to_le32((uint32_t)Blocks * BlockSize),
620  .Flags = MS_COMMAND_DIR_DATA_OUT,
621  .LUN = LUNIndex,
622  .SCSICommandLength = 10,
623  .SCSICommandData =
624  {
626  0x00, // Unused (control bits, all off)
627  (BlockAddress >> 24), // MSB of Block Address
628  (BlockAddress >> 16),
629  (BlockAddress >> 8),
630  (BlockAddress & 0xFF), // LSB of Block Address
631  0x00, // Reserved
632  0x00, // MSB of Total Blocks to Write
633  Blocks, // LSB of Total Blocks to Write
634  0x00 // Unused (control)
635  }
636  };
637 
638  if ((ErrorCode = MS_Host_SendCommand(MSInterfaceInfo, &SCSICommandBlock, BlockBuffer)) != PIPE_RWSTREAM_NoError)
639  return ErrorCode;
640 
641  return PIPE_RWSTREAM_NoError;
642 }
643 
644 #endif
645