Included is an MS-DOS program in C that does it. The code is compatible with Microsoft, Borland, Watcom and DJGPP compilers. [It is a reprint of a February article linked from the home page.]
There is no equivalent of the code for Linux or for Windows. With Linux the Serial Number can be retrieved via an ioctl() call. With Windows there need be written a Kernel Mode device driver. The Linux ioctl() code would be trivial. The Windows code would be a nightmare.
ftp://fission.dt.wdc.com/pub/standards/ata/ata-3/
ftp://ftp.symbios.com/pub/standards/io/x3t10/drafts/ata3/
Every attempt has been made to verify the following text; errors, however, may have inadvertantly infested themselves. No gaurantees or warranties are implied. The author is responsible for spellng and grammer only.
There are 7 BYTE wide input I/O ports and 1 WORD wide output (or data) port. Six of the 7 input ports are for parameters and one is for the command to be issued.
ATA I/O ports are adapter based (even if motherboards have built in IDE support).
All I/O ports are contiguous for each adapter, and there can be up to two adapters, and each adapter supports two drives, or devices. Each adapter is based at a different I/O address. These addresses are:
Adapter # Base Address 1 0x1f0 2 0x170
The 7 I/O ports that make up the parameter ports and the command port are known as the Command Block Registers, and are at offsets from the base address (starting at offset 1).
The Data Register is at offest 0 from the base address.
Offest Description (Read / Write) 1 Error / Features 2 Sector Count 3 Sector Number 4 Cylinder Low byte 5 Cylinder High byte 6 Device-Head 7 Status / Command
The Device-Head Register is used to select the adapter device by a bit in the register:
IDE Device Base Address Device Bit 1 0x1f0 0 2 0x1f0 1 3 0x170 0 4 0x170 1
Before a command is issued to a HDD (or device as the specifications call a drive) from 1 to all 6 parameters ports are written first, then the command port is written; after which the data, if any, is read from the data port.
The ATA specification defines each command as filling the Command Block Registers with data. The Command Register is always written last and when written tells the IDE device to perform the command. Not all registers are used for every command. The Identify Device Command is defined as:
Register Value 1 Features n/u (not used) 2 Sector Count n/u 3 Sector Number n/u 4 Cylinder Low byte n/u 5 Cylinder High byte n/u 6 Device-Head A0h/B0h 7 Command EChThe Device-Head Register actually shares several purposes, defined as bit-fields:
+-----------------------------------------+ | 7 | 6 | 5 | 4 | 3 2 1 0 | BIT +-----------------------------------------+ | 1 | LBA | 1 | DEV | HEAD | +-----------------------------------------+In the case of the ID Command the LBA and HEAD fields are not used (and set to zero):
A0 hexidecimal = 1010 0000 binary B0 hexidecimal = 1011 0000 binary
+-----------------------------------------------+ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +-----------------------------------------------+ | BSY | DRDY| DF | DSC | DRQ | CORR| IDX | ERR | +-----------------------------------------------+ Bit Description if true (bit == 1) 7 device is busy 6 device is ready 5 device fault error 4 device seek complete 3 data request is ready 2 correctable data error 1 vendor specific 0 error occuredThe Status Register should not be read for at least 400 nano seconds after issuing a command. The device sets BSY or DRQ to one within 400ns.
When a DWORD is transfered the least significant WORD of the DWORD is transfered and then the most significant WORD.
The ID Command returns 256 WORDs of data. When the ID Command is issued, the device sets the BSY bit to 1, prepares the 256 WORDs of data, sets the DRQ bit to 1, and generates an interrupt.
Here is the <PRE></PRE> source code, but some browsers can screw with such text so get the .C file (or the .TXT file) if that is the case with yourn.
#include <stdio.h> #include <stdlib.h> #include <dos.h> typedef unsigned short ushort; /* 16-bit */ typedef unsigned long ulong; /* 32-bit */ #pragma pack(1) /* * returned identify device data format */ typedef struct { /* word */ ushort gcfg; /* 0 general configuration */ ushort fcyl; /* 1 number of fixed cyls */ ushort rcyl; /* 2 number of removable cyls */ ushort hds; /* 3 number of heads */ ushort bpt; /* 4 bytes/track */ ushort bps; /* 5 bytes/sector */ ushort spt; /* 6 sectors/track */ ushort isg; /* 7 inter-sector gap */ ushort plo; /* 8 PLO sync field */ ushort vsw; /* 9 vendor status word */ char sn[20]; /* 10 serial number */ ushort v1; /* 20 (vendor specific) */ ushort v2; /* 21 */ ushort lbytes; /* 22 vendor specific bytes for LONG cmds */ char fr[8]; /* 23 firmware revision */ char mn[40]; /* 27 model number */ ushort v3; /* 47 */ ushort r1; /* 48 (reserved) */ ushort cap; /* 49 capabilities */ ushort r2; /* 50 */ ushort piot; /* 51 pio data transfer cycle timing mode (8:15) */ ushort r3; /* 52 */ ushort r4; /* 53 */ ushort lcyl; /* 54 number of current logical cylinders */ ushort lhd; /* 55 number of current logical heads */ ushort lsec; /* 56 number of current logical sectors per track */ ulong secs; /* 57 current capacity in sectors */ ushort secset; /* 59 */ ulong tsecs; /* 60 total number of user addressable sectors (LBA mode) */ ushort r5; /* 62 */ ushort dmam; /* 63 multiword DMA transfer mode */ ushort r6; /* 64 */ ushort mdmat; /* 65 minimum multiword DMA transfer cycle time per word */ ushort rdmat; /* 66 recommended multiword DMA transfer cycle time */ ushort mpiot; /* 67 minimum PIO transfer cycle time without flow control */ ushort mpioit; /* 68 minimum PIO transfer cycle time with IORDY flow control */ ushort r7[11]; /* 69 */ ushort majorv; /* 80 major version number */ ushort minorv; /* 81 minor version number */ ushort supp; /* 82 command set supported */ ushort eh; /* 83 */ ushort r8[44]; /* 84 */ ushort secur; /* 128 security status */ ushort v4[31]; /* 129 */ ushort r9[96]; /* 160 */ } ID_PARMS; /* * identify drive data */ union idbuf { ID_PARMS parms; unsigned short data[256]; }; #define ATA_PRI_M 0x1f0 /* base address of primary controller */ #define ATA_SEC_M 0x170 /* base address of secondary controller */ #define ATA_REG_DATA 0 /* data register */ #define ATA_REG_DEV 6 /* device register */ #define ATA_REG_CMD 7 /* command register */ #define ATA_REG_STATUS 7 /* status register */ #define DEV_OFF 4 #define ATA_DEV_NUM(dev) (0xA0 + ((dev)<<DEV_OFF)) #define ATA_CMD_GETP 0xec /* get drive parameters */ #define DELAY 50 #ifdef _MSC_VER void delay(unsigned int n); #undef DELAY #define DELAY 1 #endif int main() { int n,s; char serial[21]; union idbuf iddata; #define DEVICE ATA_DEV_NUM(0) /* Set IDE to read here */ #define ADAPTER ATA_PRI_M /* Set IDE to read here */ #ifdef TEST_IF_I_DID_STRUCT_RIGHT if (sizeof(ID_PARMS) != 256 * sizeof(ushort)) { printf("\nThe ID_PARMS struct is of the wrong size! "); printf("(%u)\n",sizeof(ID_PARMS) / sizeof(ushort)); exit(1); } #endif /* * This code is hardly ideal. It was written to understand * how to interface with an IDE device. This code does work * (for my machine anyway). */ outp(ADAPTER + ATA_REG_DEV, DEVICE); delay(DELAY); outp(ADAPTER + ATA_REG_CMD, ATA_CMD_GETP); delay(DELAY); s = inp(ADAPTER + ATA_REG_STATUS); for (n = 0; n < 256; n++) iddata.data[n] = (unsigned short)inpw(ADAPTER + ATA_REG_DATA); swab(iddata.parms.sn,serial,20); serial[20] = '\0'; printf("\"%s\"",serial); return 0; } #ifdef _MSC_VER /* * 19 millisecond resolution delay */ void delay(unsigned int n) { static unsigned int __far * volatile const pitick = /* pointer const */ (unsigned int __far *)0x0000046cL; /* object volatile */ register unsigned int u; u = n + *pitick; /* add ticks to current counter */ if (u < n) /* if the addition overflowed */ { while (*pitick > 1) /* wait for counter to overflow */ ; u = n + *pitick; /* and start again */ } while (*pitick < u) ; } #endif