SCSI ATA PASS-THROUGH commands

Has anyone used the ATA PASS-THROUGH commands that are defined in the T10 specification sat2r09? I may have a need to use those commands to access SATA drives connected to a SAS controller. I know that SATA drives work fine when connected to a SAS controller for normal operations, but I need to be able to send the full spectrum of SATA commands to the drive without it being connected to a SATA controller. The major commands that I need are vendor unique and firmware upload. I’ve done a lot of work with SAS and SATA, and even used SATA drives connected to SAS controllers, but I have as yet to use the A1h and 85h SCSI commands that comprise ATA-PASS-THROUGH.

Gary Little
xxxxx@comcast.net
C 952-454-4629
H 952-223-1349
Tain’t what you want that makes you fat, it’s what you get.

If you target to Win8 and above, ATA PASS THROUGH IOCTL is supported in Storport stack. If you need to support Win7 and earlier version of Windows, you’ll need to explore the use of this SCSI command.

-Michael Xing

Thanks for the reply Michael, but WIN 7 or WIN 8 is not an option. Also the SATA/AHCI stack is unavailable for what I am doing, because the target OS is Mac OS X and Apple does not publish the information needed to subclass to the IOAHCIFamily kernel extension. However, they do publish an interface and plugin for SCSI, hence the query about ATA PASS-THROUGH (0xA1 and 0x85). Basically, it’s an attempt to end-run Apples restrictions, and use a published interface to do what we need to do; e.g. send a vendor unique command to access log files on the drive that are maintained by the drives firmware, or upload new firmware.

Basically I’m porting code that currently works on both Windows and LINUX. So why not simply boot to one of those environments? To avoid non-repeatable errors. Drive manufacturers track numbers on returned drives, and the number of drives that are returned reporting errors that cannot be duplicated on a lab bench is rather high. Testing the drive in the same environment the customer was using can reduce those non-repeatable errors. My intent was to get a feel for how affective the ATA PASS-THROUGH commands are for accessing SATA drives connected to a system via a SAS controller from developers that may have used it. Since SATA drives are cheap storage for large server operations, I thought that some one here had experience using the ATA PASS-THROUGH commands to send a FIS to SATA drives.

Gary Little
xxxxx@comcast.net
C 952-454-4629
H 952-223-1349
Tain’t what you want that makes you fat, it’s what you get.

On Feb 18, 2013, at 4:35 PM, xxxxx@hotmail.com wrote:

If you target to Win8 and above, ATA PASS THROUGH IOCTL is supported in Storport stack. If you need to support Win7 and earlier version of Windows, you’ll need to explore the use of this SCSI command.

-Michael Xing


NTDEV is sponsored by OSR

OSR is HIRING!! See http://www.osr.com/careers

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

Gary,

I have used ATA_PASS_THROUGH (0xA1) works through
IOCTL_SCSI_PASS_THROUGH_DIRECT. In my experience passing ATA commands
works as well as passing SCSI commands.

Affective? Using them makes me feel happy. :stuck_out_tongue:

Sorry, I couldn’t resist.

I’ve used ATA_PASS_THROUGH numerous times. But never with SATA drives on a SAS controller. Waste of a good SAS controller, if you ask me. But given the on-wire protocol matters only to the SAS controller and the drive, whether the drives are SATA or SAS shouldn’t really matter, right?

Peter
OSR

Wull ? the smell checker liked it. Sheesh, so make it EEEEffective. :slight_smile:

Overall I agree with you on using SATA drives on a SAS controller. It’s mostly an attempt to salvage already existing code by simply wrapping the produced FIS in a CDB before sending it down to the interface.

Gary Little
xxxxx@comcast.net
C 952-454-4629
H 952-223-1349
Tain’t what you want that makes you fat, it’s what you get.

On Feb 21, 2013, at 11:08 AM, xxxxx@osr.com wrote:

Affective? Using them makes me feel happy. :stuck_out_tongue:

Sorry, I couldn’t resist.

I’ve used ATA_PASS_THROUGH numerous times. But never with SATA drives on a SAS controller. Waste of a good SAS controller, if you ask me. But given the on-wire protocol matters only to the SAS controller and the drive, whether the drives are SATA or SAS shouldn’t really matter, right?

Peter
OSR


NTDEV is sponsored by OSR

OSR is HIRING!! See http://www.osr.com/careers

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

Garry,

In the theory it is easy… as long as you have your own HBA driver. Apple’s HBA drivers do not allow pass-through commands for a probably valid reason.

There are two ways doing what you would like to:

  • userclient API-s of IOKitLib (well-documented, used the most)
  • using setProperties() call and dispatching it.

Typically something like that:

IOReturn MyAHCICtrllr::setProperties(OSObject *properties)
{
OSDictionary *dict;
OSNumber *number;
UInt64 value;
UInt32 CommandCode;
UInt32 CommandArgument;

dict = OSDynamicCast(OSDictionary, properties);
if (!dict)
{
IOLog(“MyAHCICtrllr::setProperties() - kIOReturnBadArgument\n”);
return (kIOReturnBadArgument);
}

number = OSDynamicCast(OSNumber, dict->getObject(“FT-COMMAND”));
if (number)
{
value = number->unsigned64BitValue();
CommandCode = (value>>32);
CommandArgument = (UInt32)(value);

IOLog((“MyAHCICtrllr::setProperties() - called with %08lX code, %08lX argument\n”, (long unsigned int)CommandCode, (long unsigned int)CommandArgument);
return (Perform_Userspace_Command(CommandCode, CommandArgument));
}

return (super::setProperties(properties));

}

Take note of the

IOReturn MyAHCICtrllr::Perform_Userspace_Command(UInt32 CommandCode, UInt32 CommandArgument)

function - that is the dispatch function of the entire user-land call.

Typically - to make sure you are on the proper side of the “gate” - unlikely anyone going to “compete” with you but better to be on the safe side:

IOReturn MyAHCICtrllr::Perform_Userspace_Command(UInt32 CommandCode, UInt32 CommandArgument)
{
IOReturn result = this->m_CmdGate->runAction((IOCommandGate::Action)
&MyAHCICtrllr::Perform_Userspace_CommandActionWrapper,
(void *) CommandCode,
(void *) CommandArgument,
(void *) 0,
(void *) 0);

return (result);
}

IOReturn MyAHCICtrllr::Perform_Userspace_CommandActionWrapper(OSObject *owner,
void *arg0,
void *arg1,
void *arg2,
void *arg3)
{

MyAHCICtrllr *me = OSDynamicCast(FTSATACtrllr, (FTSATACtrllr *)owner);
UInt64 CommandCode64 = (UInt64)arg0;
UInt64 CommandArgument64 = (UInt64)arg1;

UInt32 CommandCode = (UInt32)CommandCode64;
UInt32 CommandArgument = (UInt32)CommandArgument64;

if (me != NULL);
{
return (me->GatedPerform_Userspace_Command(CommandCode, CommandArgument));
}
}

After all that the MyAHCICtrllr::GatedPerform_Userspace_Command does something like that:

IOReturn MyAHCICtrllr::GatedPerform_Userspace_Command(UInt32 CommandCode, UInt32 CommandArgument)
{
switch (CommandCode)
{
case xxxxx1:
{
break;
}

case xxxxx2:
{
break;
}



default:
{
IOLog(false, “MyAHCICtrllr::GatedPerform_Userspace_Command(): unknown command 0x%08lX\n”, (unsigned long int)CommandCode);
return (kIOReturnBadArgument);
}
}

return (kIOReturnSuccess);
}

And every case statement is doing something you want to do from the “user-land”.

A call from the user-land looks something like that:

kern_return_t SendMyCommandToController(io_registry_entry_t regEntry, UInt32 CommandCode, UInt32 argument)
{
CFMutableDictionaryRef dictRef;
CFNumberRef numberRef;
kern_return_t kernResult;
UInt64 number = 0ULL;

number = CommandCode;

number = (number << 32);

number = number | argument;

numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &number);

dictRef = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks
);

CFDictionarySetValue(dictRef, __CFStringMakeConstantString(“My-Super-Duper-IO-Command”), numberRef);

kernResult = IORegistryEntrySetCFProperties(regEntry, dictRef);

CFRelease(numberRef);
CFRelease(dictRef);

return (kernResult);
}

So you are basically setting the entry “My-Super-Duper-IO-Command” to the value of “numberRef” within the registry associated with the driver controlling the HBA and your driver code is “catching” that and dispatching according own logic - other calls will be just transparent.

Now in order to do all that you have to have:

a) a utility which does the trick (lesser issue)
b) your own AHCI (or SAS) driver (a bigger issue).

Hope it helps… In any case, as you seeing from my other post I am in a “reverse” situation: I do have my AHCI driver under IOKit… and I am struggling now with the same for Win 7 / Win 8. Both of us having fun :wink:

Ooops, sorry - bad copy-paste.

Read:

IOReturn MyAHCICtrllr::setProperties(OSObject *properties)
{
OSDictionary *dict;
OSNumber *number;
UInt64 value;
UInt32 CommandCode;
UInt32 CommandArgument;

dict = OSDynamicCast(OSDictionary, properties);
if (!dict)
{
IOLog(“MyAHCICtrllr::setProperties() - kIOReturnBadArgument\n”);
return (kIOReturnBadArgument);
}

number = OSDynamicCast(OSNumber, dict->getObject(“My-Super-Duper-IO-Command”));
if (number)
{
value = number->unsigned64BitValue();
CommandCode = (value>>32);
CommandArgument = (UInt32)(value);

IOLog((“MyAHCICtrllr::setProperties() - called with %08lX code, %08lX
argument\n”, (long unsigned int)CommandCode, (long unsigned
int)CommandArgument);
return (Perform_Userspace_Command(CommandCode, CommandArgument));
}

return (super::setProperties(properties));

}

And:

IOReturn MyAHCICtrllr::Perform_Userspace_CommandActionWrapper(OSObject *owner,
void *arg0,
void *arg1,
void *arg2,
void *arg3)
{

MyAHCICtrllr *me = OSDynamicCast(MyAHCICtrllr, (MyAHCICtrllr *)owner);
UInt64 CommandCode64 = (UInt64)arg0;
UInt64 CommandArgument64 = (UInt64)arg1;

UInt32 CommandCode = (UInt32)CommandCode64;
UInt32 CommandArgument = (UInt32)CommandArgument64;

if (me != NULL);
{
return (me->GatedPerform_Userspace_Command(CommandCode, CommandArgument));
}
}

Sorry for the confusion.

… And (still) continuing.

With the above method you can obviously “force down the throat” of YOUR (or MINE) KEXT any size of data. The “YOUR” or “MINE” is very important because no of Apple’s KEXT-es AFAIK does that dispatch, it’s purely optional. The size of the data does not really matters because you can just call that

CFDictionarySetValue()
IORegistryEntrySetCFProperties()

as many times in the row you wish - and with every call (if you use above implementation) you transfer a 32-bit number (a “value”) and a 32-bit number (a “command”) across the user-kernel boundary without being forced to allocate a user-space buffer / map it into kernel / prepare it in the driver / etc-etc.

The format is obviously completely up to the designer - you can easily transfer, say, first an ATA task file values than 512 bytes (128 double-words) for data that task file has to take care of and at the end some more data what would tell the controller what drive / channel to address. All you need to implement in both driver and the user-land (preferably Cocoa) utility something around what I posted above. So far, this did work for us since over 10 years. The approach is, however not very good when the performance is an issue. For things like ATA/SATA service commands or (E)EPROM flashing of the HBA itself the “speed” (I mean, the lack of it) is perfect. If you want to use DMA and read/write a lot of data you will need to implement a good user-client… which is extra code you have to “hide” somewhere and more confusing to install.

… and while “me” being zero in “Perform_Userspace_CommandActionWrapper” should never happen… a bug is a bug, so there it is. I forgot to return “kIOReturnBadArgument” at the end of the “Perform_Userspace_CommandActionWrapper” if the call falls through, shame on me.

Now that bug is gone of course so if you take that code snippet - be aware of it. :open_mouth: