diff mbox

[1/1] SMART ATA Functionality

Message ID 1249673584.16514.8.camel@nibbler.dlib.indiana.edu
State Superseded
Headers show

Commit Message

Brian Wheeler Aug. 7, 2009, 7:33 p.m. UTC
For the lulz I implemented basic SMART functionality in ide.c.  smartctl
on linux recognizes it just fine and starting self tests with it
complete successfully.

Signed-off-by: Brian Wheeler <bdwheele@indiana.edu>
---
 hw/ide.c |  205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 200 insertions(+), 5 deletions(-)
diff mbox

Patch

diff --git a/hw/ide.c b/hw/ide.c
index 6cf04a6..f397b9c 100644
--- a/hw/ide.c
+++ b/hw/ide.c
@@ -372,6 +372,28 @@ 
 #define SENSE_ILLEGAL_REQUEST 5
 #define SENSE_UNIT_ATTENTION  6
 
+#define SMART_READ_DATA       0xd0
+#define SMART_READ_THRESH     0xd1
+#define SMART_ATTR_AUTOSAVE   0xd2
+#define SMART_SAVE_ATTR       0xd3
+#define SMART_EXECUTE_OFFLINE 0xd4
+#define SMART_READ_LOG        0xd5
+#define SMART_WRITE_LOG       0xd6
+#define SMART_ENABLE          0xd8
+#define SMART_DISABLE         0xd9
+#define SMART_STATUS          0xda
+
+static int smart_attributes[][5] = {
+    /* id,  flags, val, wrst, thrsh */
+    { 0x01, 0x03, 0x64, 0x64, 0x06}, /* raw read */
+    { 0x03, 0x03, 0x64, 0x64, 0x46}, /* spin up */
+    { 0x04, 0x02, 0x64, 0x64, 0x14}, /* start stop count */
+    { 0x05, 0x03, 0x64, 0x64, 0x36}, /* remapped sectors */
+    { 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+
+
 struct IDEState;
 
 typedef void EndTransferFunc(struct IDEState *);
@@ -443,6 +465,13 @@  typedef struct IDEState {
     int media_changed;
     /* for pmac */
     int is_read;
+    /* SMART */
+    uint8_t smart_enabled;
+    uint8_t smart_autosave;
+    int smart_errors;
+    uint8_t smart_selftest_count;
+    uint8_t *smart_selftest_data;
+
 } IDEState;
 
 /* XXX: DVDs that could fit on a CD will be reported as a CD */
@@ -593,14 +622,18 @@  static void ide_identify(IDEState *s)
     put_le16(p + 68, 120);
     put_le16(p + 80, 0xf0); /* ata3 -> ata6 supported */
     put_le16(p + 81, 0x16); /* conforms to ata5 */
-    put_le16(p + 82, (1 << 14));
+    /* 14=NOP supported, 0=SMART supported */
+    put_le16(p + 82, (1 << 14) | 1);
     /* 13=flush_cache_ext,12=flush_cache,10=lba48 */
     put_le16(p + 83, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10));
-    put_le16(p + 84, (1 << 14));
-    put_le16(p + 85, (1 << 14));
+    /* 14=set to 1, 1=SMART self test, 0=SMART error logging */
+    put_le16(p + 84, (1 << 14) | 0);
+    /* 14 = NOP supported, 0=SMART feature set enabled */
+    put_le16(p + 85, (1 << 14) | 1);
     /* 13=flush_cache_ext,12=flush_cache,10=lba48 */
     put_le16(p + 86, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10));
-    put_le16(p + 87, (1 << 14));
+    /* 14=set to 1, 1=smart self test, 0=smart error logging */
+    put_le16(p + 87, (1 << 14) | 0);
     put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */
     put_le16(p + 93, 1 | (1 << 14) | 0x2000);
     put_le16(p + 100, s->nb_sectors);
@@ -2565,6 +2598,162 @@  static void ide_ioport_write(void *opaque, uint32_t addr, uint32_t val)
             s->status = READY_STAT | SEEK_STAT;
             ide_set_irq(s);
             break;
+
+	case WIN_SMART:
+	    if (s->is_cdrom)
+		goto abort_cmd;
+	    if (s->hcyl != 0xc2 || s->lcyl != 0x4f)
+		goto abort_cmd;
+	    if (!s->smart_enabled && s->feature != SMART_ENABLE)
+		goto abort_cmd;
+	    switch (s->feature) {
+	    case SMART_DISABLE:
+		s->smart_enabled = 0;
+		s->status = READY_STAT | SEEK_STAT;
+		ide_set_irq(s);
+		break;
+	    case SMART_ENABLE:
+		s->smart_enabled = 1;
+		s->status = READY_STAT | SEEK_STAT;
+		ide_set_irq(s);
+		break;
+	    case SMART_ATTR_AUTOSAVE:
+		switch (s->sector) {
+		case 0x00:
+		    s->smart_autosave = 0;
+		    break;
+		case 0xf1:
+		    s->smart_autosave = 1;
+		    break;
+		default:
+		    goto abort_cmd;
+		}
+		s->status = READY_STAT | SEEK_STAT;
+		ide_set_irq(s);
+		break;
+	    case SMART_STATUS:
+		if (!s->smart_errors) {
+		    s->hcyl = 0xc2;
+		    s->lcyl = 0x4f;
+		} else {
+		    s->hcyl = 0x2c;
+		    s->lcyl = 0xf4;
+		}
+		s->status = READY_STAT | SEEK_STAT;
+		ide_set_irq(s);
+		break;
+	    case SMART_READ_THRESH:
+		memset(s->io_buffer, 0, 0x200);
+		s->io_buffer[0] = 0x01; /* smart struct version */
+		for (n=0; n<30; n++) {
+		    if (smart_attributes[n][0] == 0)
+			break;
+		    s->io_buffer[2+0+(n*12)] = smart_attributes[n][0];
+		    s->io_buffer[2+1+(n*12)] = smart_attributes[n][4];
+		}
+		for (n=0; n<511; n++) /* checksum */
+		    s->io_buffer[511] += s->io_buffer[n];
+		s->io_buffer[511] = 0x100 - s->io_buffer[511];
+		s->status = READY_STAT | SEEK_STAT;
+		ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+		ide_set_irq(s);
+		break;
+	    case SMART_READ_DATA:
+		memset(s->io_buffer, 0, 0x200);
+		s->io_buffer[0] = 0x01; /* smart struct version */
+		for (n=0; n<30; n++) {
+		    if (smart_attributes[n][0] == 0)
+			break;
+		    s->io_buffer[2+0+(n*12)] = smart_attributes[n][0];
+		    s->io_buffer[2+1+(n*12)] = smart_attributes[n][1];
+		    s->io_buffer[2+3+(n*12)] = smart_attributes[n][2];
+		    s->io_buffer[2+4+(n*12)] = smart_attributes[n][3];
+		}
+		s->io_buffer[362] = 0x02 | (s->smart_autosave?0x80:0x00);
+		if (s->smart_selftest_count == 0) {
+		    s->io_buffer[363] = 0;
+		} else {
+		    s->io_buffer[363] = 
+			s->smart_selftest_data[3 + 
+					       (s->smart_selftest_count - 1) * 
+					       24];
+		}
+		s->io_buffer[364] = 0x20; 
+		s->io_buffer[365] = 0x01; 
+		/* offline data collection capacity: execute + self-test*/
+		s->io_buffer[367] = (1<<4 | 1<<3 | 1); 
+		s->io_buffer[368] = 0x03; /* smart capability (1) */
+		s->io_buffer[369] = 0x00; /* smart capability (2) */
+		s->io_buffer[370] = 0x01; /* error logging supported */
+		s->io_buffer[372] = 0x02; /* minutes for poll short test */
+		s->io_buffer[373] = 0x36; /* minutes for poll ext test */
+		s->io_buffer[374] = 0x01; /* minutes for poll conveyance */
+
+		for (n=0; n<511; n++) 
+		    s->io_buffer[511] += s->io_buffer[n];
+		s->io_buffer[511] = 0x100 - s->io_buffer[511];
+		s->status = READY_STAT | SEEK_STAT;
+		ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+		ide_set_irq(s);
+		break;
+	    case SMART_READ_LOG:
+		switch (s->sector) {
+		case 0x01: /* summary smart error log */
+		    memset(s->io_buffer, 0, 0x200);
+		    s->io_buffer[0] = 0x01;
+		    s->io_buffer[1] = 0x00; /* no error entries */
+		    s->io_buffer[452] = s->smart_errors & 0xff;
+		    s->io_buffer[453] = (s->smart_errors & 0xff00) >> 8;
+
+		    for (n=0; n<511; n++)
+			s->io_buffer[511] += s->io_buffer[n];
+		    s->io_buffer[511] = 0x100 - s->io_buffer[511];
+		    break;
+		case 0x06: /* smart self test log */
+		    memset(s->io_buffer, 0, 0x200);
+		    s->io_buffer[0] = 0x01; 
+		    if (s->smart_selftest_count == 0) {
+			s->io_buffer[508] = 0;
+		    } else {
+			s->io_buffer[508] = s->smart_selftest_count;
+			for (n=2; n<506; n++) 
+			    s->io_buffer[n] = s->smart_selftest_data[n];
+		    }		    
+		    for (n=0; n<511; n++)
+			s->io_buffer[511] += s->io_buffer[n];
+		    s->io_buffer[511] = 0x100 - s->io_buffer[511];
+		    break;
+		default:
+		    goto abort_cmd;
+		}
+		s->status = READY_STAT | SEEK_STAT;
+		ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+		ide_set_irq(s);
+		break;
+	    case SMART_EXECUTE_OFFLINE:
+		switch (s->sector) {
+		case 0: /* off-line routine */
+		case 1: /* short self test */
+		case 2: /* extended self test */
+		    s->smart_selftest_count++;
+		    if(s->smart_selftest_count > 21)
+			s->smart_selftest_count = 0;
+		    n = 2 + (s->smart_selftest_count - 1) * 24;
+		    s->smart_selftest_data[n] = s->sector;
+		    s->smart_selftest_data[n+1] = 0x00; /* OK and finished */
+		    s->smart_selftest_data[n+2] = 0x34; /* hour count lsb */
+		    s->smart_selftest_data[n+3] = 0x12; /* hour count msb */
+		    s->status = READY_STAT | SEEK_STAT;
+		    ide_set_irq(s);
+		    break;
+		default:
+		    goto abort_cmd;
+		}
+		break;
+	    default:
+		goto abort_cmd;
+	    }
+	    break;
         default:
         abort_cmd:
             ide_abort_command(s);
@@ -2826,7 +3015,13 @@  static void ide_init2(IDEState *ide_state,
             s->heads = heads;
             s->sectors = secs;
             s->nb_sectors = nb_sectors;
-
+	    /* The SMART values should be preserved across power cycles
+	       but they aren't.  */
+	    s->smart_enabled = 1;
+	    s->smart_autosave = 1;
+	    s->smart_errors = 0;
+	    s->smart_selftest_count = 0;
+	    s->smart_selftest_data = qemu_blockalign(s->bs, 512);
             if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM) {
                 s->is_cdrom = 1;
 		bdrv_set_change_cb(s->bs, cdrom_change_cb, s);