Wednesday 16 June 2010

Windows Embedded CE: USB memory stick fix

Hi folks,

those of you who have done some work with the Windows Embedded CE USB host controller know that countless hours/days/weeks/month can be spent improving it.

One problem that I have often heard about is the USB stick support, or the lack of it. Initially about 50% of USB sticks do not work. But with these simple improvements up to about 90% of the USB sticks will work. All the mentioned files reside under _PUBLICROOT\COMMON\OAK\DRIVERS\USB\CLASS\STORAGE\DISK\SCSI2. Be sure to clone the respective files to your Platform before altering them.

1 Sending commands to device before it is available:

In most cases, the problem seems to be that the HCD tries to communicate with the device before it is available. This can be tested by inserting the device just enough for the power LED to blink, then after waiting a short time inserting the device fully. The device should work now.

The MediaChangeThread in disk.c polls for presence of USB devices, but it seems that in this case commands are sent to the device before pDevice->Flags.MediumPresent is set by the MediaChangeThread.

Fix:

There is a bug in disk.c: in the function DSK_IOControl in the DISK_IOCTL_GETINFO case in the if (pDevice->Flags.MediumPresent == FALSE) statement the memset statement has the last 2 parameters switched:

if (pDevice->Flags.MediumPresent == FALSE)
{
DEBUGMSG(ZONE_ERR,
(TEXT( "Usbdisk6!DSK_IOControl> IOCTL_GET_INFO;
media not present\r\n"
)));

// USB Memory Stick fix:
//
// BUG: last 2 parameters of memset were switched

memset((PVOID)&pDevice->DiskInfo, 0, sizeof(DISK_INFO));
memcpy((PVOID)pBuf, (PVOID)&pDevice->DiskInfo,
sizeof(DISK_INFO));
bRc = TRUE;
}


Also in disk.c, the function GetMediumInfo should return immediately if no device is present (if (!pDevice->Flags.MediumPresent)):

if (!pDevice->Flags.MediumPresent)
{
// USB Memory Stick fix:
//
// return here immediately since it will
// probably fail anyway
// but some devices do not recover well here!

break;
/*
DISK_INFO di = {0};
dwErr = ScsiReadCapacity(pDevice, &di, Lun);
if (ERROR_SUCCESS != dwErr)
break;
*/
}


Last but not least in the function ScsiTestUnitReady in scsi2.c, ScsiGetSenseData needs to be recalled upon failure after waiting a couple of milliseconds:

DWORD dwCount;


// USB Memory Stick fix:
for(dwCount = 0; dwCount < 50; dwCount ++)
{
dwErr = ScsiGetSenseData( pDevice, Lun );
DEBUGMSG(ZONE_ERR,(TEXT
("ScsiTestUnitReady ERROR:%d\n"), dwErr));
SetLastError(dwErr);
if ( dwErr == ERROR_SUCCESS )
{
break;
}
Sleep(50);
}


NOTE:
It should be enough to retry ScsiGetSenseData once, since it will return the error state of the last command forcing the second call to succeed.

2 Unhandled case in SENSE_UNIT_ATTENTION:

In the function ScsiGetSenseData in scsi2.c, some devices report an unhandled ASC in the SENSE_UNIT_ATTENTION case. The Unhandled ASC is 0x3A which is the code for ASC_MEDIUM_NOT_PRESENT. The spec does not state this as a valid responds to the SENSE_UNIT_ATTENTION command, nevertheless some devices report it.

Fix:

To take this behaviour into account, add the code used for the ASC_MEDIUM_NOT_PRESENT case to SENSE_UNIT_ATTENTION from the SENSE_NOT_READY command:

case ASC_MEDIUM_NOT_PRESENT :
pDevice->Flags.MediumPresent = FALSE;
pDevice->Flags.MediumChanged = TRUE;
pDevice->MediumType = SCSI_MEDIUM_UNKNOWN;

memset(&pDevice->DiskInfo, 0, sizeof(DISK_INFO) );

dwErr = DISK_REMOVED_ERROR;
break;


This causes the function ScsiTestUnitReady to retry the operation which is not according to spec at this point.

3 The length field of the SCSI_MODE_SENSE6 command is 0

In scsi.c in the function ScsiModeSense6, the length field (bCDB[4]) of the SCSI_MODE_SENSE6 command is not set and therefore 0. All devices except Sony and Freecom accept or ignore this fact.

Fix:

Set bCDB[4] for the SCSI_MODE_SENSE6 command. But setting it to:

bCDB[4] = sizeof(bDataBlock) will result in 0x200 which is a 0 byte.

So better set it to something like:

bCDB[4] = 0xFF;

See also my post "Windows Embedded CE: USB memory stick fix part 2".
Have fun!

5 comments:

Anonymous said...

Hello Jochen,

I've tried this and the things missing for me were ScsiGetSenseData repetition and the bCDB initialization (the other fixes were already in place including comment typos ^_^).

In the function SCSIGetSenseData in scsi.c, ...
That's ScsiGetSenseData and scsi2.c for me :).

The for(dwCount = 0; dwCount < 50; dwCount ++) appears pretty random to me, I prefer a time limit instead. I measured ca. 600ms for my "problematic" USB flashdrive and I think I'll set a 1200ms timeout plus one attempt.

Now my problem is fixed, thank you :).

Jochen Dieckfoß [MVP] said...

Hi Alex T.,

I’m glad that I could help out. I’ve written most of the code (and a document about it) back in 2007 and some of the information was also available on the net.

Thanks for pointing out the typo in the function name and file name, I just updated my post accordingly.

You are right, the for(dwCount = 0; dwCount < 50; dwCount ++) does appear pretty randomly. In my specific case the USB stick was recognized on the 2nd iteration, but there are other cases.

Best regards
-Jochen

Anonymous said...

Hi, thanks a lot for this post, also had the issues before your fix implementation:)
Best Regards,
Sergii Makovetskyi

Mark said...

Thanks for the code fixes - anyone else reading this please do not just cut and paste like I did! The post above has an error in the for loop condition:

// USB Memory Stick fix:
for(dwCount = 0; dwCount > 50; dwCount ++)

It should be dwCount < 50!!! Mark

Damian Barnett said...

Hi Mark,
Thanks for the comment. You are of course correct that it should be
// USB Memory Stick fix:
for(dwCount = 0; dwCount < 50; dwCount ++).
I went ahead and made the edit in the post to reflect this.
Sorry about that!
Damian