|
1 // Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
2 // All rights reserved. |
|
3 // This component and the accompanying materials are made available |
|
4 // under the terms of the License "Eclipse Public License v1.0" |
|
5 // which accompanies this distribution, and is available |
|
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
7 // |
|
8 // Initial Contributors: |
|
9 // Nokia Corporation - initial contribution. |
|
10 // |
|
11 // Contributors: |
|
12 // |
|
13 // Description: |
|
14 // USB Mass Storage Application - also used as an improvised boot loader mechanism |
|
15 // |
|
16 // |
|
17 |
|
18 |
|
19 |
|
20 /** |
|
21 @file |
|
22 */ |
|
23 |
|
24 #include "usbtestclient.h" |
|
25 |
|
26 #include <e32std.h> |
|
27 #include <e32svr.h> |
|
28 #include <e32cons.h> |
|
29 |
|
30 #include <usbmsshared.h> |
|
31 |
|
32 #include <d32usbc.h> |
|
33 #include <d32otgdi.h> |
|
34 |
|
35 #include <nkern/nk_trace.h> |
|
36 #include <hal.h> |
|
37 |
|
38 #include "rusbmassstorage.h" |
|
39 |
|
40 enum |
|
41 { |
|
42 EUsbDeviceStateUndefined = EUsbcDeviceStateUndefined, |
|
43 EUsbDeviceStateConfigured = EUsbcDeviceStateConfigured, |
|
44 }; |
|
45 |
|
46 static CConsoleBase* console = NULL; |
|
47 static RFs fs; |
|
48 static TInt selectedDriveIndex = 0; |
|
49 static TBuf<0x40> mountList; |
|
50 |
|
51 static TFixedArray<TBool, KMaxDrives> msfsMountedList; ///< 'true' entry corresponds to the drive with mounted MSFS.FSY |
|
52 static TFixedArray<CFileSystemDescriptor*, KMaxDrives> unmountedFsList; ///< every non-NULL entry corresponds to the unmounted original FS for the drive |
|
53 |
|
54 _LIT(KMsFsy, "USBTESTMSCLIENT.FSY"); |
|
55 _LIT(KMsFs, "MassStorageFileSystem"); |
|
56 _LIT(KOk,"OK"); |
|
57 _LIT(KError,"Error"); |
|
58 _LIT(KBytesTransferredFmt, "%c:%d/%d "); |
|
59 _LIT(KErrFmt, "Error: %d\r"); |
|
60 |
|
61 _LIT(KTxtApp,"USB TEST CLIENT"); |
|
62 _LIT(KDefPwd,"123"); |
|
63 |
|
64 //-- if defined, some useful information will be printed out via RDebug interface |
|
65 //#define LOGGING_ENABLED |
|
66 |
|
67 //----------------------------------------------------------------------------- |
|
68 /** |
|
69 prints a line to the console and copies it to the debug log if LOGGING_ENABLED |
|
70 */ |
|
71 void LogPrint(TRefByValue<const TDesC> aFmt,...) |
|
72 { |
|
73 VA_LIST list; |
|
74 VA_START(list, aFmt); |
|
75 |
|
76 TBuf<0x100> buf; |
|
77 buf.FormatList(aFmt, list); //-- ignore overflows |
|
78 |
|
79 if(console) |
|
80 console->Write(buf); |
|
81 |
|
82 #ifdef LOGGING_ENABLED |
|
83 //-- print out the line via RDebug::Print |
|
84 const TInt bufLen = buf.Length(); |
|
85 if(bufLen >0 && buf[bufLen-1] == '\n') |
|
86 { |
|
87 buf.Insert(bufLen-1, _L("\r")); |
|
88 } |
|
89 else |
|
90 { |
|
91 buf.Append(_L("\r\n")); |
|
92 } |
|
93 |
|
94 RDebug::RawPrint(buf); |
|
95 #endif |
|
96 } |
|
97 |
|
98 //----------------------------------------------------------------------------- |
|
99 /** |
|
100 prints a line to the debug log if LOGGING_ENABLED |
|
101 */ |
|
102 void Log(TRefByValue<const TDesC> aFmt,...) |
|
103 { |
|
104 #ifdef LOGGING_ENABLED |
|
105 |
|
106 VA_LIST list; |
|
107 VA_START(list, aFmt); |
|
108 |
|
109 TBuf<0x100> buf; |
|
110 buf.FormatList(aFmt, list); //-- ignore overflows |
|
111 |
|
112 //-- print out the line via RDebug::Print |
|
113 const TInt bufLen = buf.Length(); |
|
114 if(bufLen >0 && buf[bufLen-1] == '\n') |
|
115 { |
|
116 buf.Insert(bufLen-1, _L("\r")); |
|
117 } |
|
118 |
|
119 RDebug::RawPrint(buf); |
|
120 #else |
|
121 (void)aFmt; |
|
122 #endif |
|
123 } |
|
124 |
|
125 |
|
126 //----------------------------------------------------------------------------- |
|
127 |
|
128 static void Clear(int row, int count=1) |
|
129 { |
|
130 _LIT(KBlank," "); |
|
131 for(TInt i=0; i<count; i++) |
|
132 { |
|
133 console->SetPos(0,row+i); |
|
134 console->Printf(KBlank); |
|
135 } |
|
136 console->SetPos(0,row); |
|
137 } |
|
138 |
|
139 |
|
140 static void ShowDriveSelection() |
|
141 { |
|
142 console->SetPos(0,15); |
|
143 if(PropertyHandlers::allDrivesStatus.Length()/2 > selectedDriveIndex) |
|
144 { |
|
145 LogPrint(_L("Selected Drive: %c"), 'A' + PropertyHandlers::allDrivesStatus[selectedDriveIndex*2]); |
|
146 } |
|
147 else |
|
148 { |
|
149 LogPrint(_L("Selected Drive: (none)")); |
|
150 } |
|
151 } |
|
152 |
|
153 |
|
154 |
|
155 class CPeriodUpdate : public CActive |
|
156 { |
|
157 public: |
|
158 static CPeriodUpdate* NewLC(); |
|
159 private: |
|
160 CPeriodUpdate(); |
|
161 void ConstructL(); |
|
162 ~CPeriodUpdate(); |
|
163 void RunL(); |
|
164 void DoCancel(); |
|
165 |
|
166 RTimer iTimer; |
|
167 TUint iUpTime; |
|
168 }; |
|
169 |
|
170 CPeriodUpdate* CPeriodUpdate::NewLC() |
|
171 { |
|
172 CPeriodUpdate* me=new(ELeave) CPeriodUpdate(); |
|
173 CleanupStack::PushL(me); |
|
174 me->ConstructL(); |
|
175 return me; |
|
176 } |
|
177 |
|
178 CPeriodUpdate::CPeriodUpdate() |
|
179 : CActive(0), iUpTime(0) |
|
180 {} |
|
181 |
|
182 void CPeriodUpdate::ConstructL() |
|
183 { |
|
184 CActiveScheduler::Add(this); |
|
185 iTimer.CreateLocal(); |
|
186 RunL(); |
|
187 } |
|
188 |
|
189 CPeriodUpdate::~CPeriodUpdate() |
|
190 { |
|
191 Cancel(); |
|
192 } |
|
193 |
|
194 void CPeriodUpdate::DoCancel() |
|
195 { |
|
196 } |
|
197 |
|
198 void CPeriodUpdate::RunL() |
|
199 { |
|
200 SetActive(); |
|
201 // Print RAM usage & up time |
|
202 |
|
203 iUpTime++; |
|
204 TUint totmins=(iUpTime/60); |
|
205 TUint tothrs=(totmins/60); |
|
206 TInt mem=0; |
|
207 if (HAL::Get(HALData::EMemoryRAMFree, mem)==KErrNone) |
|
208 { |
|
209 console->SetPos(0,22); |
|
210 console->Printf(_L("mem (bytes) : %d\n"), mem); |
|
211 console->Printf(_L("up time : %dh:%dm:%ds\n"), |
|
212 tothrs, totmins%60, iUpTime%60); |
|
213 } |
|
214 iTimer.After(iStatus, 1000000); |
|
215 } |
|
216 |
|
217 //----------------------------------------------------------------------------- |
|
218 /** |
|
219 Dismounts the originally mounted FS and optional primary extension from the drive and stores |
|
220 this information in the FS descriptor |
|
221 |
|
222 @return on success returns a pointer to the instantinated FS descriptor |
|
223 */ |
|
224 static CFileSystemDescriptor* DoDismountOrginalFS(RFs& aFs, TInt aDrive) |
|
225 { |
|
226 TInt nRes; |
|
227 TBuf<128> fsName; |
|
228 TBuf<128> primaryExtName; |
|
229 TBool bDrvSync = EFalse; |
|
230 |
|
231 Log(_L("# DoDismountOrginalFS drv:%d\n"), aDrive); |
|
232 |
|
233 //-- 1. get file system name |
|
234 nRes = aFs.FileSystemName(fsName, aDrive); |
|
235 if(nRes != KErrNone) |
|
236 {//-- probably no file system installed at all |
|
237 return NULL; |
|
238 } |
|
239 |
|
240 //-- 2. find out if the drive sync/async |
|
241 TPckgBuf<TBool> drvSyncBuf; |
|
242 nRes = aFs.QueryVolumeInfoExt(aDrive, EIsDriveSync, drvSyncBuf); |
|
243 if(nRes == KErrNone) |
|
244 { |
|
245 bDrvSync = drvSyncBuf(); |
|
246 } |
|
247 |
|
248 //-- 3. find out primary extension name if it is present; we will need to add it againt when mounting the FS |
|
249 //-- other extensions (non-primary) are not supported yet |
|
250 nRes = aFs.ExtensionName(primaryExtName, aDrive, 0); |
|
251 if(nRes != KErrNone) |
|
252 { |
|
253 primaryExtName.SetLength(0); |
|
254 } |
|
255 |
|
256 //-- 3.1 check if the drive has non-primary extensions, fail in this case, because this FS can't be mounted back normally |
|
257 nRes = aFs.ExtensionName(primaryExtName, aDrive, 1); |
|
258 if(nRes == KErrNone) |
|
259 { |
|
260 LogPrint(_L("Non-primary extensions are not supported!\n")); |
|
261 return NULL; |
|
262 } |
|
263 |
|
264 Log(_L("# DoDismountOrginalFS FS:%S, Prim ext:%S, synch:%d\n"), &fsName, &primaryExtName, bDrvSync); |
|
265 |
|
266 //-- create FS descriptor and dismount the FS |
|
267 CFileSystemDescriptor* pFsDesc = NULL; |
|
268 |
|
269 TRAP(nRes, pFsDesc = CFileSystemDescriptor::NewL(fsName, primaryExtName, bDrvSync)); |
|
270 if(nRes != KErrNone) |
|
271 return NULL; //-- OOM ? |
|
272 |
|
273 nRes = aFs.DismountFileSystem(fsName, aDrive); |
|
274 if(nRes != KErrNone) |
|
275 { |
|
276 delete pFsDesc; |
|
277 pFsDesc = NULL; |
|
278 Log(_L("# DoDismountOrginalFS Dismounting Err:%d\n"), nRes); |
|
279 } |
|
280 |
|
281 return pFsDesc; |
|
282 } |
|
283 |
|
284 //----------------------------------------------------------------------------- |
|
285 /** |
|
286 Tries to restore the original FS on the drive using the FS descriptor provided |
|
287 @return standard error code. |
|
288 */ |
|
289 static TInt DoRestoreFS(RFs& aFs, TInt aDrive, CFileSystemDescriptor* apFsDesc) |
|
290 { |
|
291 TInt nRes; |
|
292 |
|
293 Log(_L("# DoRestoreFS drv:%d\n"), aDrive); |
|
294 |
|
295 //-- 1. check that there is no FS installed |
|
296 { |
|
297 TBuf<128> fsName; |
|
298 nRes = aFs.FileSystemName(fsName, aDrive); |
|
299 if(nRes == KErrNone) |
|
300 {//-- probably no file system installed at all |
|
301 Log(_L("# This drive already has FS intalled:%S \n"), &fsName); |
|
302 return KErrAlreadyExists; |
|
303 } |
|
304 } |
|
305 |
|
306 TPtrC ptrN (apFsDesc->FsName()); |
|
307 TPtrC ptrExt(apFsDesc->PrimaryExtName()); |
|
308 Log(_L("# Mounting FS:%S, Prim ext:%S, synch:%d\n"), &ptrN, &ptrExt, apFsDesc->DriveIsSynch()); |
|
309 |
|
310 if(ptrExt.Length() >0) |
|
311 {//-- there is a primary extension to be mounted |
|
312 nRes = aFs.AddExtension(ptrExt); |
|
313 if(nRes != KErrNone && nRes != KErrAlreadyExists) |
|
314 { |
|
315 return nRes; |
|
316 } |
|
317 |
|
318 nRes = aFs.MountFileSystem(ptrN, ptrExt, aDrive, apFsDesc->DriveIsSynch()); |
|
319 } |
|
320 else |
|
321 { |
|
322 nRes = aFs.MountFileSystem(ptrN, aDrive, apFsDesc->DriveIsSynch()); |
|
323 } |
|
324 |
|
325 if(nRes != KErrNone) |
|
326 { |
|
327 Log(_L("# Mount failed! code:%d\n"),nRes); |
|
328 } |
|
329 |
|
330 return nRes; |
|
331 } |
|
332 |
|
333 |
|
334 //----------------------------------------------------------------------------- |
|
335 /** |
|
336 Dismount the original FS from the drive and mount MsFS instead |
|
337 */ |
|
338 static void MountMsFs(TInt driveNumber) |
|
339 { |
|
340 TInt x = console->WhereX(); |
|
341 TInt y = console->WhereY(); |
|
342 |
|
343 //-- 1. try dismounting the original FS |
|
344 CFileSystemDescriptor* fsDesc = DoDismountOrginalFS(fs, driveNumber); |
|
345 unmountedFsList[driveNumber] = fsDesc; |
|
346 |
|
347 console->SetPos(0, 10); |
|
348 |
|
349 if(fsDesc) |
|
350 { |
|
351 TPtrC ptrN(fsDesc->FsName()); |
|
352 LogPrint(_L("drv:%d FS:%S Dismounted OK"),driveNumber, &ptrN); |
|
353 } |
|
354 else |
|
355 { |
|
356 LogPrint(_L("drv:%d Dismount FS Failed!"),driveNumber); |
|
357 } |
|
358 |
|
359 console->ClearToEndOfLine(); |
|
360 |
|
361 //-- 2. try to mount the "MSFS" |
|
362 TInt error; |
|
363 error = fs.MountFileSystem(KMsFs, driveNumber); |
|
364 console->SetPos(0, 11); |
|
365 LogPrint(_L("MSFS Mount: %S (%d)"), (error?&KError:&KOk), error); |
|
366 console->ClearToEndOfLine(); |
|
367 |
|
368 if (!error) |
|
369 msfsMountedList[driveNumber] = ETrue; |
|
370 |
|
371 // restore console position |
|
372 console->SetPos(x,y); |
|
373 } |
|
374 |
|
375 //----------------------------------------------------------------------------- |
|
376 /** |
|
377 Dismount MsFS and mount the original FS |
|
378 */ |
|
379 static TInt RestoreMount(TInt driveNumber) |
|
380 { |
|
381 TInt err = KErrNone; |
|
382 |
|
383 TInt x = console->WhereX(); |
|
384 TInt y = console->WhereY(); |
|
385 |
|
386 //-- 1. try dismounting the "MSFS" |
|
387 if (msfsMountedList[driveNumber]) |
|
388 { |
|
389 err = fs.DismountFileSystem(KMsFs, driveNumber); |
|
390 console->SetPos(0, 11); |
|
391 LogPrint(_L("MSFS Dismount:%S (%d)"), (err?&KError:&KOk), err); |
|
392 console->ClearToEndOfLine(); |
|
393 if (err) |
|
394 return err; |
|
395 |
|
396 msfsMountedList[driveNumber] = EFalse; |
|
397 } |
|
398 |
|
399 //-- 2. try to mount the original FS back |
|
400 CFileSystemDescriptor* fsDesc = unmountedFsList[driveNumber]; |
|
401 if(fsDesc) |
|
402 { |
|
403 err = DoRestoreFS(fs, driveNumber, fsDesc); |
|
404 |
|
405 TPtrC ptrN(fsDesc->FsName()); |
|
406 console->SetPos(0, 10); |
|
407 LogPrint(_L("%S Mount: %S (%d)"), &ptrN, (err?&KError:&KOk), err); |
|
408 console->ClearToEndOfLine(); |
|
409 |
|
410 delete fsDesc; |
|
411 unmountedFsList[driveNumber] = NULL; |
|
412 } |
|
413 |
|
414 |
|
415 // restore console position |
|
416 console->SetPos(x,y); |
|
417 return err; |
|
418 } |
|
419 |
|
420 ////////////////////////////////////////////////////////////////////////////// |
|
421 // |
|
422 // CPropertyWatch |
|
423 // An active object that tracks changes to the KUsbMsDriveState properties |
|
424 // |
|
425 ////////////////////////////////////////////////////////////////////////////// |
|
426 |
|
427 CPropertyWatch* CPropertyWatch::NewLC(TUsbMsDriveState_Subkey aSubkey, PropertyHandlers::THandler aHandler) |
|
428 { |
|
429 CPropertyWatch* me=new(ELeave) CPropertyWatch(aHandler); |
|
430 CleanupStack::PushL(me); |
|
431 me->ConstructL(aSubkey); |
|
432 return me; |
|
433 } |
|
434 |
|
435 CPropertyWatch::CPropertyWatch(PropertyHandlers::THandler aHandler) |
|
436 : CActive(0), iHandler(aHandler) |
|
437 {} |
|
438 |
|
439 void CPropertyWatch::ConstructL(TUsbMsDriveState_Subkey aSubkey) |
|
440 { |
|
441 User::LeaveIfError(iProperty.Attach(KUsbMsDriveState_Category, aSubkey)); |
|
442 CActiveScheduler::Add(this); |
|
443 // initial subscription and process current property value |
|
444 RunL(); |
|
445 } |
|
446 |
|
447 CPropertyWatch::~CPropertyWatch() |
|
448 { |
|
449 Cancel(); |
|
450 iProperty.Close(); |
|
451 } |
|
452 |
|
453 void CPropertyWatch::DoCancel() |
|
454 { |
|
455 iProperty.Cancel(); |
|
456 } |
|
457 |
|
458 void CPropertyWatch::RunL() |
|
459 { |
|
460 // resubscribe before processing new value to prevent missing updates |
|
461 iProperty.Subscribe(iStatus); |
|
462 SetActive(); |
|
463 |
|
464 iHandler(iProperty); |
|
465 } |
|
466 |
|
467 ////////////////////////////////////////////////////////////////////////////// |
|
468 // |
|
469 // CUsbWatch |
|
470 // |
|
471 ////////////////////////////////////////////////////////////////////////////// |
|
472 |
|
473 CUsbWatch* CUsbWatch::NewLC(RUsb& aUsb) |
|
474 { |
|
475 CUsbWatch* me=new(ELeave) CUsbWatch(aUsb); |
|
476 CleanupStack::PushL(me); |
|
477 me->ConstructL(); |
|
478 return me; |
|
479 } |
|
480 |
|
481 CUsbWatch::CUsbWatch(RUsb& aUsb) |
|
482 : |
|
483 CActive(0), |
|
484 iUsb(aUsb), |
|
485 iUsbDeviceState(EUsbDeviceStateUndefined), |
|
486 iWasConfigured(EFalse) |
|
487 {} |
|
488 |
|
489 void CUsbWatch::ConstructL() |
|
490 { |
|
491 CActiveScheduler::Add(this); |
|
492 RunL(); |
|
493 } |
|
494 |
|
495 CUsbWatch::~CUsbWatch() |
|
496 { |
|
497 Cancel(); |
|
498 // iUsb.DeviceStateNotificationCancel(); |
|
499 iUsb.AlternateDeviceStatusNotifyCancel(); |
|
500 } |
|
501 |
|
502 void CUsbWatch::DoCancel() |
|
503 { |
|
504 // iUsb.DeviceStateNotificationCancel(); |
|
505 iUsb.AlternateDeviceStatusNotifyCancel(); |
|
506 } |
|
507 |
|
508 static TBool IsDriveConnected(TInt driveStatusIndex) |
|
509 { |
|
510 TInt driveStatus = PropertyHandlers::allDrivesStatus[2*driveStatusIndex+1]; |
|
511 return driveStatus >= EUsbMsDriveState_Connected ? ETrue : EFalse; |
|
512 } |
|
513 |
|
514 static TChar DriveNumberToLetter(TInt driveNumber) |
|
515 { |
|
516 TChar driveLetter = '?'; |
|
517 fs.DriveToChar(driveNumber, driveLetter); |
|
518 return driveLetter; |
|
519 } |
|
520 |
|
521 static TBool IsDriveInMountList(TUint driveLetter) |
|
522 { |
|
523 TUint16 driveLetter16 = static_cast<TUint16>(driveLetter); |
|
524 return(!mountList.Length() || KErrNotFound != mountList.Find(&driveLetter16, 1)); |
|
525 } |
|
526 |
|
527 void CUsbWatch::RunL() |
|
528 { |
|
529 // RDebug::Print(_L(">> CUsbWatch[%d] %d"), iUsbDeviceState, iWasConfigured); |
|
530 |
|
531 // const TUint stateMask = 0xFF; |
|
532 // iUsb.DeviceStateNotification(stateMask, iUsbDeviceState, iStatus); |
|
533 iUsb.AlternateDeviceStatusNotify(iStatus, iUsbDeviceState); |
|
534 SetActive(); |
|
535 |
|
536 //RDebug::Print(_L("CUsbWatch DeviceStateNotification: iUsbDeviceState=%d"), iUsbDeviceState); |
|
537 |
|
538 // If the cable is disconnected, unmount all the connected drives. |
|
539 if(iWasConfigured && iUsbDeviceState == EUsbDeviceStateUndefined) |
|
540 { |
|
541 for(TInt i=0; i<PropertyHandlers::allDrivesStatus.Length()/2; i++) |
|
542 { |
|
543 if(IsDriveConnected(i)) |
|
544 { |
|
545 //RDebug::Print(_L("CUsbWatch calling RestoreMount")); |
|
546 RestoreMount(PropertyHandlers::allDrivesStatus[2*i]); |
|
547 } |
|
548 } |
|
549 iWasConfigured = EFalse; |
|
550 } |
|
551 |
|
552 // If cable is connected, mount all drives in the auto-mount list. This is |
|
553 // done for performance, since if this is not done here, mounting will |
|
554 // happen later after each drive enters the Connecting state. |
|
555 if (iUsbDeviceState == EUsbDeviceStateConfigured) |
|
556 { |
|
557 for (TInt i=0; i<PropertyHandlers::allDrivesStatus.Length()/2; i++) |
|
558 { |
|
559 TInt driveNumber = PropertyHandlers::allDrivesStatus[2*i]; |
|
560 if (!IsDriveConnected(i) && IsDriveInMountList(DriveNumberToLetter(driveNumber))) |
|
561 { |
|
562 //RDebug::Print(_L("CUsbWatch calling MountMsFs")); |
|
563 MountMsFs(driveNumber); |
|
564 } |
|
565 } |
|
566 iWasConfigured = ETrue; |
|
567 } |
|
568 } |
|
569 |
|
570 ////////////////////////////////////////////////////////////////////////////// |
|
571 // |
|
572 // PropertyHandlers |
|
573 // |
|
574 ////////////////////////////////////////////////////////////////////////////// |
|
575 |
|
576 TBuf8<16> PropertyHandlers::allDrivesStatus; |
|
577 TUsbMsBytesTransferred PropertyHandlers::iKBytesRead; |
|
578 TUsbMsBytesTransferred PropertyHandlers::iKBytesWritten; |
|
579 TInt PropertyHandlers::iMediaError; |
|
580 |
|
581 void PropertyHandlers::Read(RProperty& aProperty) |
|
582 { |
|
583 Transferred(aProperty, iKBytesRead); |
|
584 } |
|
585 |
|
586 void PropertyHandlers::Written(RProperty& aProperty) |
|
587 { |
|
588 Transferred(aProperty, iKBytesWritten); |
|
589 } |
|
590 |
|
591 void PropertyHandlers::Transferred(RProperty& aProperty, TUsbMsBytesTransferred& aReadOrWritten) |
|
592 { |
|
593 console->SetPos(0,1); |
|
594 console->Printf(_L("KB R/W: ")); |
|
595 TInt err = aProperty.Get(aReadOrWritten); |
|
596 if(err == KErrNone) |
|
597 { |
|
598 for(TInt i = 0; i < allDrivesStatus.Length()/2; i++) |
|
599 { |
|
600 console->Printf(KBytesTransferredFmt, |
|
601 (char)DriveNumberToLetter(allDrivesStatus[2*i]), iKBytesRead[i], iKBytesWritten[i]); |
|
602 } |
|
603 console->ClearToEndOfLine(); |
|
604 } |
|
605 else |
|
606 { |
|
607 console->Printf(KErrFmt, err); |
|
608 } |
|
609 } |
|
610 |
|
611 void PropertyHandlers::DriveStatus(RProperty& aProperty) |
|
612 { |
|
613 // RDebug::Print(_L(">> PropertyHandlers::DriveStatus")); |
|
614 TInt err = aProperty.Get(allDrivesStatus); |
|
615 console->SetPos(0,0); |
|
616 if(err == KErrNone) |
|
617 { |
|
618 console->Printf(_L("Status: ")); |
|
619 for(TInt i = 0; i < allDrivesStatus.Length()/2; i++) |
|
620 { |
|
621 TInt driveNumber = allDrivesStatus[2*i]; |
|
622 TInt driveStatus = allDrivesStatus[2*i+1]; |
|
623 TChar driveLetter = DriveNumberToLetter(driveNumber); |
|
624 |
|
625 // RDebug::Print(_L("%c:%d "), (char)driveLetter, driveStatus); |
|
626 |
|
627 switch(driveStatus) |
|
628 { |
|
629 case EUsbMsDriveState_Disconnected: |
|
630 { |
|
631 LogPrint(_L("%c:%d:Disconnected "), (char)driveLetter, driveStatus); |
|
632 break; |
|
633 } |
|
634 case EUsbMsDriveState_Connecting: |
|
635 { |
|
636 LogPrint(_L("%c:%d:Connecting "), (char)driveLetter, driveStatus); |
|
637 break; |
|
638 } |
|
639 case EUsbMsDriveState_Connected: |
|
640 { |
|
641 LogPrint(_L("%c:%d:Connected "), (char)driveLetter, driveStatus); |
|
642 break; |
|
643 } |
|
644 case EUsbMsDriveState_Disconnecting: |
|
645 { |
|
646 LogPrint(_L("%c:%d:Disconnecting"), (char)driveLetter, driveStatus); |
|
647 break; |
|
648 } |
|
649 case EUsbMsDriveState_Active: |
|
650 { |
|
651 LogPrint(_L("%c:%d:Active "), (char)driveLetter, driveStatus); |
|
652 break; |
|
653 } |
|
654 case EUsbMsDriveState_Locked: |
|
655 { |
|
656 LogPrint(_L("%c:%d:Locked "), (char)driveLetter, driveStatus); |
|
657 break; |
|
658 } |
|
659 case EUsbMsDriveState_MediaNotPresent: |
|
660 { |
|
661 LogPrint(_L("%c:%d:Not Present "), (char)driveLetter, driveStatus); |
|
662 break; |
|
663 } |
|
664 case EUsbMsDriveState_Removed: |
|
665 { |
|
666 LogPrint(_L("%c:%d:Removed "), (char)driveLetter, driveStatus); |
|
667 break; |
|
668 } |
|
669 case EUsbMsDriveState_Error: |
|
670 { |
|
671 LogPrint(_L("%c:%d:Error "), (char)driveLetter, driveStatus); |
|
672 break; |
|
673 } |
|
674 default : |
|
675 { |
|
676 LogPrint(_L("%c:%d:Unknown "), (char)driveLetter, driveStatus); |
|
677 break; |
|
678 } |
|
679 } |
|
680 |
|
681 if(IsDriveInMountList(driveLetter)) |
|
682 { |
|
683 if (driveStatus == EUsbMsDriveState_Connecting) |
|
684 { |
|
685 MountMsFs(driveNumber); |
|
686 } |
|
687 else if (driveStatus == EUsbMsDriveState_Disconnected) |
|
688 { |
|
689 RestoreMount(driveNumber); |
|
690 } |
|
691 else |
|
692 { |
|
693 //RDebug::Print(_L("PropertyHandlers::DriveStatus: nothing to do")); |
|
694 } |
|
695 } |
|
696 else |
|
697 { |
|
698 //RDebug::Print(_L("PropertyHandlers::DriveStatus: %c: is not in mountList\n"), driveLetter); |
|
699 } |
|
700 } |
|
701 } |
|
702 else |
|
703 { |
|
704 LogPrint(KErrFmt, err); |
|
705 } |
|
706 |
|
707 //RDebug::Print(_L("<< PropertyHandlers::DriveStatus")); |
|
708 } |
|
709 |
|
710 void PropertyHandlers::MediaError(RProperty& aProperty) |
|
711 { |
|
712 TInt err = aProperty.Get(iMediaError); |
|
713 if (err != KErrNone) |
|
714 { |
|
715 // RDebug::Printf("RProperty::Get returned %d", err); |
|
716 return; |
|
717 } |
|
718 |
|
719 //RDebug::Printf("PropertyHandlers::MediaError %x", iMediaError); |
|
720 |
|
721 TInt x = console->WhereX(); |
|
722 TInt y = console->WhereY(); |
|
723 Clear(27,1); |
|
724 LogPrint(_L("Media Error %x"), iMediaError); |
|
725 // restore console position |
|
726 console->SetPos(x,y); |
|
727 } |
|
728 |
|
729 ////////////////////////////////////////////////////////////////////////////// |
|
730 // |
|
731 // CMessageKeyProcessor |
|
732 // |
|
733 ////////////////////////////////////////////////////////////////////////////// |
|
734 CMessageKeyProcessor::CMessageKeyProcessor(CConsoleBase* aConsole) |
|
735 : CActive(CActive::EPriorityUserInput), iConsole(aConsole) |
|
736 { |
|
737 } |
|
738 |
|
739 CMessageKeyProcessor* CMessageKeyProcessor::NewLC(CConsoleBase* aConsole) |
|
740 { |
|
741 CMessageKeyProcessor* self=new (ELeave) CMessageKeyProcessor(aConsole); |
|
742 CleanupStack::PushL(self); |
|
743 self->ConstructL(); |
|
744 return self; |
|
745 } |
|
746 |
|
747 CMessageKeyProcessor* CMessageKeyProcessor::NewL(CConsoleBase* aConsole) |
|
748 { |
|
749 CMessageKeyProcessor* self = NewLC(aConsole); |
|
750 CleanupStack::Pop(); |
|
751 return self; |
|
752 } |
|
753 |
|
754 void CMessageKeyProcessor::ConstructL() |
|
755 { |
|
756 // Add to active scheduler |
|
757 CActiveScheduler::Add(this); |
|
758 RequestCharacter(); |
|
759 } |
|
760 |
|
761 void CMessageKeyProcessor::MakePassword(TMediaPassword &aPassword) |
|
762 { |
|
763 // Create password with same format as eshell and S60 |
|
764 TBuf<3> password(KDefPwd); |
|
765 |
|
766 // fill aPassword with contents of password, not converting to ASCII |
|
767 const TInt byteLen = password.Length() * 2; |
|
768 aPassword.Copy(reinterpret_cast<const TUint8 *>(password.Ptr()), byteLen); |
|
769 } |
|
770 |
|
771 CMessageKeyProcessor::~CMessageKeyProcessor() |
|
772 { |
|
773 // Make sure we're cancelled |
|
774 Cancel(); |
|
775 } |
|
776 |
|
777 void CMessageKeyProcessor::DoCancel() |
|
778 { |
|
779 iConsole->ReadCancel(); |
|
780 } |
|
781 |
|
782 void CMessageKeyProcessor::RunL() |
|
783 { |
|
784 // Handle completed request |
|
785 ProcessKeyPress(TChar(iConsole->KeyCode())); |
|
786 } |
|
787 |
|
788 void CMessageKeyProcessor::RequestCharacter() |
|
789 { |
|
790 // A request is issued to the CConsoleBase to accept a |
|
791 // character from the keyboard. |
|
792 iConsole->Read(iStatus); |
|
793 SetActive(); |
|
794 } |
|
795 |
|
796 void CMessageKeyProcessor::ProcessKeyPress(TChar aChar) |
|
797 { |
|
798 |
|
799 TInt error = KErrNone; |
|
800 |
|
801 aChar.UpperCase(); |
|
802 switch(aChar) |
|
803 { |
|
804 case 'Q': |
|
805 case EKeyEscape: |
|
806 { |
|
807 TInt err = KErrNone; |
|
808 for(TInt j=0; j<KMaxDrives; j++) |
|
809 { |
|
810 err = RestoreMount(j); |
|
811 |
|
812 if (err) |
|
813 { |
|
814 // Mount is busy/locked and can not be restored. |
|
815 break; |
|
816 } |
|
817 |
|
818 } |
|
819 |
|
820 if (err == KErrNone) |
|
821 { |
|
822 CActiveScheduler::Stop(); |
|
823 return; |
|
824 } |
|
825 |
|
826 } |
|
827 break; |
|
828 |
|
829 #if defined(_DEBUG) |
|
830 case 'T': |
|
831 iTraceEnable = !iTraceEnable; |
|
832 if (iTraceEnable) // 0x44008401 |
|
833 User::SetDebugMask(KHARDWARE|KDLL|KSCRATCH|KPOWER|KMEMTRACE); |
|
834 else |
|
835 User::SetDebugMask(0); |
|
836 break; |
|
837 #endif |
|
838 |
|
839 case 'D': |
|
840 if(++selectedDriveIndex >= PropertyHandlers::allDrivesStatus.Length()/2) |
|
841 { |
|
842 selectedDriveIndex = 0; |
|
843 } |
|
844 ShowDriveSelection(); |
|
845 break; |
|
846 |
|
847 case 'M': |
|
848 if(PropertyHandlers::allDrivesStatus.Length()) |
|
849 { |
|
850 MountMsFs(PropertyHandlers::allDrivesStatus[selectedDriveIndex*2]); |
|
851 } |
|
852 break; |
|
853 |
|
854 case 'U': |
|
855 if(PropertyHandlers::allDrivesStatus.Length()) |
|
856 { |
|
857 RestoreMount(PropertyHandlers::allDrivesStatus[selectedDriveIndex*2]); |
|
858 } |
|
859 break; |
|
860 |
|
861 case 'L': |
|
862 { |
|
863 // lock unprotected drive |
|
864 TMediaPassword password; |
|
865 MakePassword(password); |
|
866 |
|
867 _LIT(KEmpty, ""); |
|
868 TMediaPassword nul; |
|
869 nul.Copy(KEmpty); |
|
870 error = fs.LockDrive(PropertyHandlers::allDrivesStatus[selectedDriveIndex*2], |
|
871 nul, password, ETrue); |
|
872 console->SetPos(0,9); |
|
873 LogPrint(_L("LockDrive %S (%d)"), (error?&KError:&KOk), error); |
|
874 break; |
|
875 } |
|
876 |
|
877 case 'I': |
|
878 { |
|
879 // lock password protected drive |
|
880 TMediaPassword password; |
|
881 MakePassword(password); |
|
882 error = fs.LockDrive(PropertyHandlers::allDrivesStatus[selectedDriveIndex*2], |
|
883 password, password, ETrue); |
|
884 console->SetPos(0,9); |
|
885 LogPrint(_L("LockDrive %S (%d)"), (error?&KError:&KOk), error); |
|
886 break; |
|
887 } |
|
888 |
|
889 case 'N': |
|
890 { |
|
891 TMediaPassword password; |
|
892 MakePassword(password); |
|
893 error = fs.UnlockDrive(PropertyHandlers::allDrivesStatus[selectedDriveIndex*2], |
|
894 password, ETrue); |
|
895 Clear(9); |
|
896 LogPrint(_L("UnlockDrive %S (%d)"), (error?&KError:&KOk), error); |
|
897 } |
|
898 break; |
|
899 |
|
900 case 'C': |
|
901 { |
|
902 TMediaPassword password; |
|
903 MakePassword(password); |
|
904 error = fs.ClearPassword(PropertyHandlers::allDrivesStatus[selectedDriveIndex*2], |
|
905 password); |
|
906 Clear(9); |
|
907 LogPrint(_L("ClearPassword %S (%d)"), (error?&KError:&KOk), error); |
|
908 } |
|
909 break; |
|
910 default: |
|
911 break; |
|
912 } |
|
913 RequestCharacter(); |
|
914 } |
|
915 |
|
916 |
|
917 ////////////////////////////////////////////////////////////////////////////// |
|
918 // |
|
919 // Application entry point |
|
920 // |
|
921 ////////////////////////////////////////////////////////////////////////////// |
|
922 static void RunAppL() |
|
923 { |
|
924 |
|
925 TInt error = KErrUnknown; |
|
926 |
|
927 //RDebug::Print(_L("USBMSAPP: Creating console\n")); |
|
928 console = Console::NewL(KTxtApp,TSize(KConsFullScreen,KConsFullScreen)); |
|
929 CleanupStack::PushL(console); |
|
930 |
|
931 console->SetPos(0,2); |
|
932 console->Printf(_L("========================================")); |
|
933 |
|
934 // Command line: list of drive letters to auto-mount (all if not specified) |
|
935 User::CommandLine(mountList); |
|
936 mountList.UpperCase(); |
|
937 |
|
938 CActiveScheduler* sched = new(ELeave) CActiveScheduler; |
|
939 CleanupStack::PushL(sched); |
|
940 CActiveScheduler::Install(sched); |
|
941 |
|
942 fs.Connect(); |
|
943 CleanupClosePushL(fs); |
|
944 |
|
945 _LIT(KMountAllDefault,"(all)"); |
|
946 console->SetPos(0,3); |
|
947 LogPrint(_L("Drives to auto-mount: %S"), (mountList.Length() ? &mountList : &KMountAllDefault)); |
|
948 |
|
949 // Add MS file system |
|
950 error = fs.AddFileSystem(KMsFsy); |
|
951 if(error != KErrNone && error != KErrAlreadyExists) |
|
952 { |
|
953 //RDebug::Print(_L("AddFileSystem failed, err=%d\n"), error); |
|
954 User::Leave(error); |
|
955 } |
|
956 console->SetPos(0,4); |
|
957 LogPrint(_L("MSFS file system:\tAdded OK\n")); |
|
958 |
|
959 RUsb usb; |
|
960 |
|
961 // Load the logical device |
|
962 _LIT(KDriverFileName,"EUSBC.LDD"); |
|
963 error = User::LoadLogicalDevice(KDriverFileName); |
|
964 if (error != KErrAlreadyExists) |
|
965 User::LeaveIfError(error); |
|
966 |
|
967 error = usb.Open(0); |
|
968 User::LeaveIfError(error); |
|
969 |
|
970 _LIT(KOtgdiLddFilename, "otgdi"); |
|
971 // Check for OTG support |
|
972 TBuf8<KUsbDescSize_Otg> otg_desc; |
|
973 error = usb.GetOtgDescriptor(otg_desc); |
|
974 if (!(error == KErrNotSupported || error == KErrNone)) |
|
975 { |
|
976 LogPrint(_L("Error %d while fetching OTG descriptor"), error); |
|
977 User::Leave(-1); |
|
978 return; |
|
979 } |
|
980 |
|
981 // On an OTG device we have to start the OTG driver, otherwise the Client |
|
982 // stack will remain disabled forever. |
|
983 if (error == KErrNotSupported) |
|
984 { |
|
985 CleanupClosePushL(usb); |
|
986 User::Leave(-1); |
|
987 } |
|
988 |
|
989 error = User::LoadLogicalDevice(KOtgdiLddFilename); |
|
990 if (error != KErrNone) |
|
991 { |
|
992 LogPrint(_L("Error %d on loading OTG LDD"), error); |
|
993 User::Leave(-1); |
|
994 return; |
|
995 } |
|
996 |
|
997 RUsbOtgDriver iOtgPort; |
|
998 |
|
999 error = iOtgPort.Open(); |
|
1000 if (error != KErrNone) |
|
1001 { |
|
1002 LogPrint(_L("Error %d on opening OTG port"), error); |
|
1003 User::Leave(-1); |
|
1004 return; |
|
1005 } |
|
1006 error = iOtgPort.StartStacks(); |
|
1007 if (error != KErrNone) |
|
1008 { |
|
1009 LogPrint(_L("Error %d on starting USB stack"), error); |
|
1010 User::Leave(-1); |
|
1011 return; |
|
1012 } |
|
1013 |
|
1014 CleanupClosePushL(usb); |
|
1015 |
|
1016 // RDebug::Print(_L("USBMSAPP: Create active objects\n")); |
|
1017 CMessageKeyProcessor::NewLC(console); |
|
1018 CPropertyWatch::NewLC(EUsbMsDriveState_KBytesRead, PropertyHandlers::Read); |
|
1019 CPropertyWatch::NewLC(EUsbMsDriveState_KBytesWritten, PropertyHandlers::Written); |
|
1020 CPropertyWatch::NewLC(EUsbMsDriveState_DriveStatus, PropertyHandlers::DriveStatus); |
|
1021 CPropertyWatch::NewLC(EUsbMsDriveState_MediaError, PropertyHandlers::MediaError); |
|
1022 CUsbWatch::NewLC(usb); |
|
1023 CPeriodUpdate::NewLC(); |
|
1024 |
|
1025 RUsbMassStorage UsbMs; |
|
1026 TBuf<8> t_vendorId(_L("vendor")); |
|
1027 TBuf<16> t_productId(_L("product")); |
|
1028 TBuf<4> t_productRev(_L("1.00")); |
|
1029 |
|
1030 TMassStorageConfig msConfig; |
|
1031 msConfig.iVendorId.Copy(t_vendorId); |
|
1032 msConfig.iProductId.Copy(t_productId); |
|
1033 msConfig.iProductRev.Copy(t_productRev); |
|
1034 |
|
1035 // console->Printf(_L("Connect to Mass Storage")); |
|
1036 error = UsbMs.Connect(); |
|
1037 User::LeaveIfError(error); |
|
1038 |
|
1039 // console->Printf(_L("Start Mass Storage")); |
|
1040 error = UsbMs.Start(msConfig); |
|
1041 User::LeaveIfError(error); |
|
1042 |
|
1043 TBuf8<KUsbDescSize_Device> deviceDescriptor; |
|
1044 error = usb.GetDeviceDescriptor(deviceDescriptor); |
|
1045 User::LeaveIfError(error); |
|
1046 |
|
1047 const TInt KUsbSpecOffset = 2; |
|
1048 const TInt KUsbDeviceClassOffset = 4; |
|
1049 const TInt KUsbVendorIdOffset = 8; |
|
1050 const TInt KUsbProductIdOffset = 10; |
|
1051 const TInt KUsbDevReleaseOffset = 12; |
|
1052 //Change the USB spec number to 2.00 |
|
1053 deviceDescriptor[KUsbSpecOffset] = 0x00; |
|
1054 deviceDescriptor[KUsbSpecOffset+1] = 0x02; |
|
1055 //Change the Device Class, Device SubClass and Device Protocol |
|
1056 deviceDescriptor[KUsbDeviceClassOffset] = 0x00; |
|
1057 deviceDescriptor[KUsbDeviceClassOffset+1] = 0x00; |
|
1058 deviceDescriptor[KUsbDeviceClassOffset+2] = 0x00; |
|
1059 //Change the device vendor ID (VID) to 0x0E22 (Symbian) |
|
1060 deviceDescriptor[KUsbVendorIdOffset] = 0x22; // little endian |
|
1061 deviceDescriptor[KUsbVendorIdOffset+1] = 0x0E; |
|
1062 //Change the device product ID (PID) to 0x1111 |
|
1063 deviceDescriptor[KUsbProductIdOffset] = 0x12; |
|
1064 deviceDescriptor[KUsbProductIdOffset+1] = 0x11; |
|
1065 //Change the device release number to 3.05 |
|
1066 deviceDescriptor[KUsbDevReleaseOffset] = 0x05; |
|
1067 deviceDescriptor[KUsbDevReleaseOffset+1] = 0x03; |
|
1068 error = usb.SetDeviceDescriptor(deviceDescriptor); |
|
1069 User::LeaveIfError(error); |
|
1070 |
|
1071 // Remove possible Remote-Wakup support in Configuration descriptor, |
|
1072 // so that we can use the MSC device also easily for Chapter9 testing. |
|
1073 TBuf8<KUsbDescSize_Config> configDescriptor; |
|
1074 error = usb.GetConfigurationDescriptor(configDescriptor); |
|
1075 User::LeaveIfError(error); |
|
1076 const TInt KConfDesc_AttribOffset = 7; |
|
1077 configDescriptor[KConfDesc_AttribOffset] &= ~KUsbDevAttr_RemoteWakeup; |
|
1078 error = usb.SetConfigurationDescriptor(configDescriptor); |
|
1079 User::LeaveIfError(error); |
|
1080 |
|
1081 _LIT16(productID_L, "Symbian USB Mass Storage Device (Base)"); |
|
1082 TBuf16<KUsbStringDescStringMaxSize / 2> productID(productID_L); |
|
1083 error = usb.SetProductStringDescriptor(productID); |
|
1084 User::LeaveIfError(error); |
|
1085 |
|
1086 TRequestStatus enum_status; |
|
1087 console->SetPos(0,5); |
|
1088 LogPrint(_L("Re-enumerating...\n")); |
|
1089 usb.ReEnumerate(enum_status); |
|
1090 User::LeaveIfError(error); |
|
1091 console->SetPos(0,5); |
|
1092 User::WaitForRequest(enum_status); |
|
1093 if(enum_status.Int() == KErrNone) |
|
1094 LogPrint(_L("Re-enumeration Done\n")); |
|
1095 else |
|
1096 LogPrint(_L("Re-enumeration not successfully done\n")); |
|
1097 |
|
1098 |
|
1099 console->SetPos(0,14); |
|
1100 TBuf<3>password(KDefPwd); |
|
1101 LogPrint(_L("Password: %S"), &password); |
|
1102 |
|
1103 ShowDriveSelection(); |
|
1104 |
|
1105 console->SetPos(0,17); |
|
1106 |
|
1107 _LIT(KMsgTitleB,"Menu: q=quit d=chg drv\n m=mount u=unmount\n l=lock i=lock n=unlock\n c=clr pwd"); |
|
1108 |
|
1109 |
|
1110 //RDebug::Print(_L("USBMSAPP: Start CActiveScheduler\n")); |
|
1111 |
|
1112 console->Printf(KMsgTitleB); |
|
1113 |
|
1114 CActiveScheduler::Start(); |
|
1115 |
|
1116 error = UsbMs.Stop(); |
|
1117 User::LeaveIfError(error); |
|
1118 UsbMs.Close(); |
|
1119 error = fs.RemoveFileSystem(KMsFs); |
|
1120 User::LeaveIfError(error); |
|
1121 |
|
1122 CleanupStack::PopAndDestroy(11); |
|
1123 |
|
1124 iOtgPort.StopStacks(); |
|
1125 iOtgPort.Close(); |
|
1126 error = User::FreeLogicalDevice(RUsbOtgDriver::Name()); |
|
1127 User::LeaveIfError(error); |
|
1128 |
|
1129 error = User::FreeLogicalDevice(_L("USBC")); |
|
1130 User::LeaveIfError(error); |
|
1131 |
|
1132 } |
|
1133 |
|
1134 GLDEF_C TInt E32Main() |
|
1135 { |
|
1136 __UHEAP_MARK; |
|
1137 CTrapCleanup* cleanup=CTrapCleanup::New(); |
|
1138 |
|
1139 msfsMountedList.Reset(); |
|
1140 unmountedFsList.Reset(); |
|
1141 |
|
1142 |
|
1143 TRAPD(error,RunAppL()); |
|
1144 __ASSERT_ALWAYS(!error, User::Panic(KTxtApp, error)); |
|
1145 |
|
1146 delete cleanup; |
|
1147 __UHEAP_MARKEND; |
|
1148 return 0; |
|
1149 } |
|
1150 |
|
1151 |
|
1152 //----------------------------------------------------------------------------- |
|
1153 |
|
1154 CFileSystemDescriptor::~CFileSystemDescriptor() |
|
1155 { |
|
1156 iFsName.Close(); |
|
1157 iPrimaryExtName.Close(); |
|
1158 } |
|
1159 |
|
1160 //----------------------------------------------------------------------------- |
|
1161 CFileSystemDescriptor* CFileSystemDescriptor::NewL(const TDesC& aFsName, const TDesC& aPrimaryExtName, TBool aDrvSynch) |
|
1162 { |
|
1163 CFileSystemDescriptor* pSelf = new (ELeave) CFileSystemDescriptor; |
|
1164 |
|
1165 CleanupStack::PushL(pSelf); |
|
1166 |
|
1167 pSelf->iFsName.CreateMaxL(aFsName.Length()); |
|
1168 pSelf->iFsName.Copy(aFsName); |
|
1169 |
|
1170 pSelf->iPrimaryExtName.CreateMaxL(aPrimaryExtName.Length()); |
|
1171 pSelf->iPrimaryExtName.Copy(aPrimaryExtName); |
|
1172 |
|
1173 pSelf->iDriveSynch = aDrvSynch; |
|
1174 |
|
1175 CleanupStack::Pop(); |
|
1176 |
|
1177 return pSelf; |
|
1178 } |