Introduction
This tip is about setting the Advanced Power Management level for your laptop drive in Windows. Well, some hard drives create quite annoying sounds (like hissing and clicks) due to aggressive APM system. I've bought a Hitachi HGST drive recently and it creates these nasty sounds and when you are working in silence (or sleeping near), it's very annoying. It's said that it may also damage your HDD (at least it will decrease its life). There are several programs but they either aren't open or vague ports from Linux. I was surprised by the fact that there is no such program. So the old saying is true: If you want to do something better - do it by yourself.
Background
To set APM, you have to send ATA commands to a hard drive. This must be done with DeviceIoControl
and for this, you should install Windows DDK with its libraries but we are not going to develop a driver so it would be better to create own small header (atahdd.h in source) with several IOCTL constants and data types. To learn more about ATA commands, see ATA spec.
Basically, you open HDD device with CreateFile("\\.\PhisycalDrive0 ...),
fill in the ATA_PASS_THROUGH_EX
structure (with the data buffer if necessary) and send it to drive with DeviceIoControl(hDrive,IOCTL_ATA_PASS_THROUGH...)
with an appropriate IOCTL code.
here is an example of Disable APM command:
hDrive = CreateFile(dskName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
0, OPEN_EXISTING, 0, NULL);
BOOL rez = false;
DWORD size1, bytesRet;
ATA_PASS_THROUGH_EX aptex;
aptex.Length = sizeof ATA_PASS_THROUGH_EX;
aptex.AtaFlags = ATA_FLAGS_DATA_IN;
aptex.DataTransferLength = 512;
aptex.DataBufferOffset = sizeof ATA_PASS_THROUGH_EX;
aptex.TimeOutValue = 1;
aptex.CurrentTaskFile.bCommandReg = ATA_SETFEATURES;
size1 = sizeof ATA_PASS_THROUGH_EX;
aptex.CurrentTaskFile.bFeaturesReg = SETFEATURES_DIS_APM;
aptex.CurrentTaskFile.bSectorCountReg = 0;
bytesRet = 0;
rez = DeviceIoControl(hDrive, IOCTL_ATA_PASS_THROUGH, &aptex, size1, &aptex,
sizeof ATA_PASS_THROUGH, &bytesRet, NULL);
We place the ATA command and subcommand codes to CurrTaskFile
member of ATA_PASS_THROUGH_EX
structure which is an IDEREGS
struct. Not all fields are necessary - it depends on ATA command. You must understand what you're doing - for example don't send WRITE SECTORS command without a reason.
typedef struct _IDEREGS
{
UCHAR bFeaturesReg;
UCHAR bSectorCountReg;
UCHAR bSectorNumberReg;
UCHAR bCylLowReg;
UCHAR bCylHighReg;
UCHAR bDriveHeadReg;
UCHAR bCommandReg;
UCHAR bReserved;
} IDEREGS;
For ATA SETFEATURES command, we don't need a data buffer so we use ATA_PASS_THROUGH_EX
structure without the buffer. Other ATA commands, like ATA IDENTIFYDEVICE returns data from device, so we need to provide an output buffer. In our case, we might just add 512 bytes of data to the end of ATA_PASS_THROGH_EX
structure. You must set DataTransferLength
to 512 for IDENTIFY DEVICE command and DataBufferOffset
to sizeof ATA_PASS_THROUGH_EX
.
typedef struct _ATA_PASS_THROUGH_EX_WITH_BUFFERS
{
USHORT Length;
USHORT AtaFlags;
BYTE PathId;
BYTE TargetId;
BYTE Lun;
BYTE ReservedAsUchar;
ULONG DataTransferLength;
ULONG TimeOutValue;
ULONG ReservedAsUlong;
ULONG_PTR DataBufferOffset;
IDEREGS PreviousTaskFile;
IDEREGS CurrentTaskFile;
UCHAR ucDataBuf[512];
} ATA_PASS_THROUGH_EX_WITH_BUFFERS;
And here is an example of getting device identification data from HDD:
BOOL rez = false;
DWORD size1, bytesRet;
ATA_PASS_THROUGH_EX_WITH_BUFFERS aptexb;
void *ptr;
aptexb.Length = sizeof ATA_PASS_THROUGH_EX;
aptexb.AtaFlags = ATA_FLAGS_DATA_IN;
aptexb.DataTransferLength = 512;
aptexb.DataBufferOffset = sizeof ATA_PASS_THROUGH_EX;
aptexb.CurrentTaskFile.bCommandReg = ATA_IDENTIFYDEVICE;
aptexb.CurrentTaskFile.bSectorCountReg = 1;
aptexb.TimeOutValue = 1;
size1 = sizeof ATA_PASS_THROUGH_EX;
bytesRet = 0;
rez = DeviceIoControl(hDrive, IOCTL_ATA_PASS_THROUGH, &aptexb, size1, &aptexb,
sizeof ATA_PASS_THROUGH_EX_WITH_BUFFERS, &bytesRet, NULL);
ptr = (void *)(&aptexb.ucDataBuf);
memcpy(hddid, ptr, sizeof IDENTIFY_DEVICE_DATA);
Don't miss with sizes in DeviceIoControl
according to your command. See more on MSDN:
To get current APM level, you must read device identification data from drive. The format of this struct
is quite tricky (it's called IDENTIFY_DEVICE_DATA
in Windows DDK) but I took it from hdreg.h file for Linux because you won't find the CurAPMValues
field in Microsoft version. The struct
also contains a lot of useful data (for example APM support by HDD) - see atahdd.h in project.
Points of Interest
1. The difference in data alignment between OS and Hardware.
The funny thing about some data blocks which are returned by the drive is that they use 1-byte data alignment - but Windows don't know about that. So when you're defining these structures you must also define the data alignment with #pragma pack(push,1)
and back #pragma pack(pop)
like this:
#pragma pack(push, 1)
typedef struct _SMART_ATTRIBUTE
{
UCHAR Id;
USHORT StatusFlags;
UCHAR CurrValue;
UCHAR WorstValue;
UCHAR RawData[6];
UCHAR Rezerved;
} SMART_ATTRIBUTE;
typedef struct _SMART_DATA
{
WORD Version;
SMART_ATTRIBUTE Attributes[30];
} SMART_DATA;
#pragma pack(pop)
2. Output buffer for DeviceIoControl IOCTL_ATA_PASS_THROUGH
with 'Non-Data' ATA commands.
MS DDK says that you don't need to provide input/output buffers along with the ATA_PASS_THROUGH_EX structure if an ATA command doesn't require data. So for the ATA DISABLE/SET APM commands ( which are 'Non-Data' commands) you might use ATA_PASS_THROUGH_EX structure without buffers, like this:
rez = DeviceIoControl(hDrive, IOCTL_ATA_PASS_THROUGH, &aptex, sizeof ATA_PASS_THROUGH_EX, &aptex,
sizeof ATA_PASS_THROUGH_EX, &bytesRet, NULL);
This worked fine with the Intel SATA driver, but after I uninstalled it this DeviceIoControl
call fails. Now I have a MS 'ATA Channel 0' driver and must provide an output buffer even if the ATA DISABLE/SET APM commands don't get/return any data. I checked this on the four PCs and result was the same. It seems different ATA drivers have different understanding of ATA specification.
rez = DeviceIoControl(hDrive, IOCTL_ATA_PASS_THROUGH, &aptexb, sizeof ATA_PASS_THROUGH_EX, &aptexb,
sizeof ATA_PASS_THROUGH_EX_WITH_BUFFERS, &bytesRet, NULL);
And now the code works fine with both MS and Intel drivers. So it would be a good idea to provide an output buffer even if the driver does't return any data. And here is the full example from code:
BOOL SetAPM(HANDLE hDrive, WORD APMVal, BOOL Disable)
{
BOOL rez = false;
DWORD size1, bytesRet;
ATA_PASS_THROUGH_EX_WITH_BUFFERS aptex;
aptex.Length = sizeof ATA_PASS_THROUGH_EX;
aptex.AtaFlags = ATA_FLAGS_DATA_IN;
aptex.DataTransferLength = 512;
aptex.DataBufferOffset = sizeof ATA_PASS_THROUGH_EX;
aptex.TimeOutValue = 3;
aptex.CurrentTaskFile.bCommandReg = WIN_SETFEATURES;
aptex.CurrentTaskFile.bSectorCountReg = 0;
size1 = sizeof ATA_PASS_THROUGH_EX;
if (Disable)
{
aptex.CurrentTaskFile.bFeaturesReg = SETFEATURES_DIS_APM;
}
else
{
aptex.CurrentTaskFile.bFeaturesReg = SETFEATURES_EN_APM;
aptex.CurrentTaskFile.bSectorCountReg = (BYTE)APMVal;
}
bytesRet = 0;
rez = DeviceIoControl(hDrive, IOCTL_ATA_PASS_THROUGH, &aptex, size1, &aptex,
sizeof ATA_PASS_THROUGH_EX_WITH_BUFFERS, &bytesRet, NULL);
return rez;
}
Conclusion.
Well it looks scary but I think everyone can fill several fields in struct
and call one or two API functions ... The main thing is - don't send wrong commands to your HDD.
And don't forget that you need the administrative rights in Windows7-8 to open HDD device. If you want to disable APM for HDD, then you'll have to create a task which will run "apmdisable.exe -dis
" with maximum rights at startup (or at logon) because after powering off the hard drive will return to its default settings. To get command-line keys for apmdisable.exe, simple run it.
Future Improvements
You may also acquire the SMART data from the HDD in a similar way (you just use an another ATA command). It would be good to check your HDD at startup to see if there are any problems. And you may turn on/off the HDD features with other commands. We even might finish with the thing like hdparm for Linux.
History
- 08-25-2014 I've updated source and article due to serious bug with the output buffer in DeviceIoControl
which had led to the 'Invalid Parameters' error in DeviceIoControl (see "Points of Interest" section above). - 19-09-2013 There was a nasty bug with data alignment in structures so I've updated source. And now it can
read the SMART data from device. - 19-09-2013 First release.