diff --git a/kernel/ata.c b/kernel/ata.c new file mode 100644 index 0000000..8be75f3 --- /dev/null +++ b/kernel/ata.c @@ -0,0 +1,533 @@ +/* +Copyright (C) 2015-2019 The University of Notre Dame +This software is distributed under the GNU General Public License. +See the file LICENSE for details. +*/ + +#include "interrupt.h" +#include "console.h" +#include "ioports.h" +#include "clock.h" +#include "string.h" +#include "ata.h" +#include "device.h" +#include "process.h" +#include "mutex.h" + +#define ATA_IRQ0 32+14 +#define ATA_IRQ1 32+15 +#define ATA_IRQ2 32+11 +#define ATA_IRQ3 32+9 + +#define ATA_BASE0 0x1F0 +#define ATA_BASE1 0x170 +#define ATA_BASE2 0x1E8 +#define ATA_BASE3 0x168 + +#define ATA_TIMEOUT 5000 +#define ATA_IDENTIFY_TIMEOUT 1000 + +#define ATA_DATA 0 /* data register */ +#define ATA_ERROR 1 /* error register */ +#define ATA_COUNT 2 /* sectors to transfer */ +#define ATA_SECTOR 3 /* sector number */ +#define ATA_CYL_LO 4 /* low byte of cylinder number */ +#define ATA_CYL_HI 5 /* high byte of cylinder number */ +#define ATA_FDH 6 /* flags, drive and head */ +#define ATA_STATUS 7 +#define ATA_COMMAND 7 +#define ATA_CONTROL 0x206 + +#define ATA_FLAGS_ECC 0x80 /* enable error correction */ +#define ATA_FLAGS_LBA 0x40 /* enable linear addressing */ +#define ATA_FLAGS_SEC 0x20 /* enable 512-byte sectors */ +#define ATA_FLAGS_SLV 0x10 /* address the slave drive */ + +#define ATA_STATUS_BSY 0x80 /* controller busy */ +#define ATA_STATUS_RDY 0x40 /* drive ready */ +#define ATA_STATUS_WF 0x20 /* write fault */ +#define ATA_STATUS_SC 0x10 /* seek complete (obsolete) */ +#define ATA_STATUS_DRQ 0x08 /* data transfer request */ +#define ATA_STATUS_CRD 0x04 /* corrected data */ +#define ATA_STATUS_IDX 0x02 /* index pulse */ +#define ATA_STATUS_ERR 0x01 /* error */ + +#define ATA_COMMAND_IDLE 0x00 +#define ATA_COMMAND_READ 0x20 /* read data */ +#define ATA_COMMAND_WRITE 0x30 /* write data */ +#define ATA_COMMAND_IDENTIFY 0xec + +#define ATAPI_COMMAND_IDENTIFY 0xa1 +#define ATAPI_COMMAND_PACKET 0xa0 + +#define ATAPI_FEATURE 1 +#define ATAPI_IRR 2 +#define ATAPI_SAMTAG 3 +#define ATAPI_COUNT_LO 4 +#define ATAPI_COUNT_HI 5 +#define ATAPI_DRIVE 6 + +#define SCSI_READ10 0x28 +#define SCSI_SENSE 0x03 + +#define ATA_CONTROL_RESET 0x04 +#define ATA_CONTROL_DISABLEINT 0x02 + +static const int ata_base[4] = { ATA_BASE0, ATA_BASE0, ATA_BASE1, ATA_BASE1 }; + +static int ata_interrupt_active = 0; +static struct list queue = { 0, 0 }; + +static struct mutex ata_mutex = MUTEX_INIT; +static int identify_in_progress = 0; + +static struct ata_count counters = {{0}}; + +struct ata_count ata_stats() +{ + return counters; +} + +static void ata_interrupt(int intr, int code) +{ + ata_interrupt_active = 1; + process_wakeup_all(&queue); +} + +void ata_reset(int id) +{ + outb(ATA_CONTROL_RESET, ata_base[id] + ATA_CONTROL); + clock_wait(1); + outb(0, ata_base[id] + ATA_CONTROL); + clock_wait(1); +} + +static int ata_wait(int id, int mask, int state) +{ + clock_t start, elapsed; + int t; + + int timeout_millis = identify_in_progress ? ATA_IDENTIFY_TIMEOUT : ATA_TIMEOUT; + + start = clock_read(); + + while(1) { + t = inb(ata_base[id] + ATA_STATUS); + if((t & mask) == state) { + return 1; + } + if(t & ATA_STATUS_ERR) { + printf("ata: error\n"); + ata_reset(id); + return 0; + } + elapsed = clock_diff(start, clock_read()); + int elapsed_millis = elapsed.seconds * 1000 + elapsed.millis; + if(elapsed_millis > timeout_millis) { + if(!identify_in_progress) { + printf("ata: timeout\n"); + } + ata_reset(id); + return 0; + } + process_yield(); + } +} + +static void ata_pio_read(int id, void *buffer, int size) +{ + uint16_t *wbuffer = (uint16_t *) buffer; + while(size > 0) { + *wbuffer = inw(ata_base[id] + ATA_DATA); + wbuffer++; + size -= 2; + } +} + +static void ata_pio_write(int id, const void *buffer, int size) +{ + uint16_t *wbuffer = (uint16_t *) buffer; + while(size > 0) { + outw(*wbuffer, ata_base[id] + ATA_DATA); + wbuffer++; + size -= 2; + } +} + +static int ata_begin(int id, int command, int nblocks, int offset) +{ + int base = ata_base[id]; + int sector, clow, chigh, flags; + + // enable error correction and linear addressing + flags = ATA_FLAGS_ECC | ATA_FLAGS_LBA | ATA_FLAGS_SEC; + + // turn on the slave bit for odd-numbered drives + if(id % 2) + flags |= ATA_FLAGS_SLV; + + // slice up the linear address in order to fit in the arguments + sector = (offset >> 0) & 0xff; + clow = (offset >> 8) & 0xff; + chigh = (offset >> 16) & 0xff; + flags |= (offset >> 24) & 0x0f; + + // wait for the disk to calm down + if(!ata_wait(id, ATA_STATUS_BSY, 0)) + return 0; + + // get the attention of the proper disk + outb(flags, base + ATA_FDH); + + // wait again for the disk to indicate ready + // special case: ATAPI identification does not raise RDY flag + + int ready; + if(command == ATAPI_COMMAND_IDENTIFY) { + ready = ata_wait(id, ATA_STATUS_BSY, 0); + } else { + ready = ata_wait(id, ATA_STATUS_BSY | ATA_STATUS_RDY, ATA_STATUS_RDY); + } + + if(!ready) + return 0; + + // send the arguments + outb(0, base + ATA_CONTROL); + outb(nblocks, base + ATA_COUNT); + outb(sector, base + ATA_SECTOR); + outb(clow, base + ATA_CYL_LO); + outb(chigh, base + ATA_CYL_HI); + outb(flags, base + ATA_FDH); + + // execute the command + outb(command, base + ATA_COMMAND); + + return 1; +} + +static int ata_read_unlocked(int id, void *buffer, int nblocks, int offset) +{ + int i; + if(!ata_begin(id, ATA_COMMAND_READ, nblocks, offset)) + return 0; + + // XXX On fast virtual hardware, waiting for the interrupt + // doesn't work b/c it has already arrived before we get here. + // For now, busy wait until a fix is in place. + + // if(ata_interrupt_active) process_wait(&queue); + + for(i = 0; i < nblocks; i++) { + if(!ata_wait(id, ATA_STATUS_DRQ, ATA_STATUS_DRQ)) + return 0; + ata_pio_read(id, buffer, ATA_BLOCKSIZE); + buffer = ((char *) buffer) + ATA_BLOCKSIZE; + offset++; + } + if(!ata_wait(id, ATA_STATUS_BSY, 0)) + return 0; + return nblocks; +} + +int ata_read(int id, void *buffer, int nblocks, int offset) +{ + int result; + mutex_lock(&ata_mutex); + result = ata_read_unlocked(id, buffer, nblocks, offset); + mutex_unlock(&ata_mutex); + counters.blocks_read[id] += nblocks; + if (current) { + current->stats.blocks_read += nblocks; + current->stats.bytes_read += nblocks*ATA_BLOCKSIZE; + } + return result; +} + +static int atapi_begin(int id, void *data, int length) +{ + int base = ata_base[id]; + int flags; + + // enable error correction and linear addressing + flags = ATA_FLAGS_ECC | ATA_FLAGS_LBA | ATA_FLAGS_SEC; + + // turn on the slave bit for odd-numbered drives + if(id % 2) + flags |= ATA_FLAGS_SLV; + + // wait for the disk to calm down + if(!ata_wait(id, ATA_STATUS_BSY, 0)) + return 0; + + // get the attention of the proper disk + outb(flags, base + ATA_FDH); + + // wait again for the disk to indicate ready + if(!ata_wait(id, ATA_STATUS_BSY, 0)) + return 0; + + // send the arguments + outb(0, base + ATAPI_FEATURE); + outb(0, base + ATAPI_IRR); + outb(0, base + ATAPI_SAMTAG); + outb(length & 0xff, base + ATAPI_COUNT_LO); + outb(length >> 8, base + ATAPI_COUNT_HI); + + // execute the command + outb(ATAPI_COMMAND_PACKET, base + ATA_COMMAND); + + // wait for ready + if(!ata_wait(id, ATA_STATUS_BSY | ATA_STATUS_DRQ, ATA_STATUS_DRQ)) + return 0; + + // send the ATAPI packet + ata_pio_write(id, data, length); + + return 1; +} + +static int atapi_read_unlocked(int id, void *buffer, int nblocks, int offset) +{ + uint8_t packet[12]; + int length = sizeof(packet); + int i; + + packet[0] = SCSI_READ10; + packet[1] = 0; + packet[2] = offset >> 24; + packet[3] = offset >> 16; + packet[4] = offset >> 8; + packet[5] = offset >> 0; + packet[6] = 0; + packet[7] = nblocks >> 8; + packet[8] = nblocks >> 0; + packet[9] = 0; + packet[10] = 0; + packet[11] = 0; + + if(!atapi_begin(id, packet, length)) + return 0; + + // XXX On fast virtual hardware, waiting for the interrupt + // doesn't work b/c it has already arrived before we get here. + // For now, busy wait until a fix is in place. + + // if(ata_interrupt_active) process_wait(&queue); + + for(i = 0; i < nblocks; i++) { + if(!ata_wait(id, ATA_STATUS_DRQ, ATA_STATUS_DRQ)) + return 0; + ata_pio_read(id, buffer, ATAPI_BLOCKSIZE); + buffer = ((char *) buffer) + ATAPI_BLOCKSIZE; + offset++; + } + + return 1; +} + +int atapi_read(int id, void *buffer, int nblocks, int offset) +{ + int result; + mutex_lock(&ata_mutex); + result = atapi_read_unlocked(id, buffer, nblocks, offset); + mutex_unlock(&ata_mutex); + counters.blocks_read[id] += nblocks; + if (current) { + current->stats.blocks_read += nblocks; + current->stats.bytes_read += nblocks * ATAPI_BLOCKSIZE; + } + return result; +} + +static int ata_write_unlocked(int id, const void *buffer, int nblocks, int offset) +{ + int i; + if(!ata_begin(id, ATA_COMMAND_WRITE, nblocks, offset)) + return 0; + for(i = 0; i < nblocks; i++) { + if(!ata_wait(id, ATA_STATUS_DRQ, ATA_STATUS_DRQ)) + return 0; + ata_pio_write(id, buffer, ATA_BLOCKSIZE); + buffer = ((char *) buffer) + ATA_BLOCKSIZE; + offset++; + } + // XXX On fast virtual hardware, waiting for the interrupt + // doesn't work b/c it has already arrived before we get here. + // For now, busy wait until a fix is in place. + + // if(ata_interrupt_active) process_wait(&queue); + + if(!ata_wait(id, ATA_STATUS_BSY, 0)) + return 0; + return nblocks; +} + +int ata_write(int id, const void *buffer, int nblocks, int offset) +{ + int result; + mutex_lock(&ata_mutex); + result = ata_write_unlocked(id, buffer, nblocks, offset); + mutex_unlock(&ata_mutex); + counters.blocks_written[id] += nblocks; + if (current) { + current->stats.blocks_written += nblocks; + current->stats.bytes_written += nblocks * ATA_BLOCKSIZE; + } + return result; +} + +/* +ata_probe sends an IDENTIFY DEVICE command to the device. +If a device is connected, it will respond with 512 bytes +of identifying data, described on page 48 of the ATA-3 standard. +If no response comes within the timeout window, we assume +the the device is simply not connected. +*/ + +static int ata_identify(int id, int command, void *buffer) +{ + int result; + identify_in_progress = 1; + if(ata_begin(id, command, 0, 0) && ata_wait(id, ATA_STATUS_DRQ, ATA_STATUS_DRQ)) { + ata_pio_read(id, buffer, 512); + result = 1; + } else { + result = 0; + } + identify_in_progress = 0; + return result; +} + + +static int ata_probe_internal( int id, int kind, int *nblocks, int *blocksize, char *name ) +{ + uint16_t buffer[256]; + char *cbuffer = (char *) buffer; + + /* + First check for 0xff in the controller status register, + which would indicate that there is nothing attached. + */ + + uint8_t t = inb(ata_base[id] + ATA_STATUS); + if(t == 0xff) { + printf("ata unit %d: nothing attached\n", id); + return 0; + } + + /* Now reset the unit to check for register signatures. */ + ata_reset(id); + + /* Clear the buffer to receive the identify data. */ + memset(cbuffer, 0, 512); + + int result = 0; + + /* Do either an ATA or ATAPI identify, or do both if kind is zero */ + + if(kind==ATA_COMMAND_IDENTIFY || kind==0) { + result = ata_identify(id, ATA_COMMAND_IDENTIFY, cbuffer); + if(result) { + *nblocks = buffer[1] * buffer[3] * buffer[6]; + printf("%d logical cylinders\n", buffer[1]); + printf("%d logical heads\n", buffer[3]); + printf("%d logical sectors/track\n", buffer[6]); + *blocksize = ATA_BLOCKSIZE; + } + } + + if(kind==ATAPI_COMMAND_IDENTIFY || (kind==0 && result==0) ) { + result = ata_identify(id, ATAPI_COMMAND_IDENTIFY, cbuffer); + if(result) { + // XXX use SCSI sense to get media size + *nblocks = 337920; + *blocksize = ATAPI_BLOCKSIZE; + } + } + + if(!result) { + printf("ata unit %d: not connected\n", id); + return 0; + } + + /* Now byte-swap the data so as the generate byte-ordered strings */ + uint32_t i; + for(i = 0; i < 512; i += 2) { + t = cbuffer[i]; + cbuffer[i] = cbuffer[i + 1]; + cbuffer[i + 1] = t; + } + cbuffer[256] = 0; + + /* Vendor supplied name is at byte 54 */ + strcpy(name,&cbuffer[54]); + name[40] = 0; + + /* Get disk size in megabytes*/ + uint32_t mbytes = (*nblocks) / KILO * (*blocksize) / KILO; + + printf("%s unit %d: %s %u sectors %u MB %s\n", + (*blocksize)==512 ? "ata" : "atapi", + id, + (*blocksize)==512 ? "disk" : "cdrom", + *nblocks, mbytes, name); + return 1; +} + +int ata_probe( int id, int *nblocks, int *blocksize, char *name ) +{ + return ata_probe_internal(id,ATA_COMMAND_IDENTIFY,nblocks,blocksize,name); +} + +int atapi_probe( int id, int *nblocks, int *blocksize, char *name ) +{ + return ata_probe_internal(id,ATAPI_COMMAND_IDENTIFY,nblocks,blocksize,name); +} + +static struct device_driver ata_driver = { + .name = "ata", + .probe = ata_probe, + .read = ata_read, + .read_nonblock = ata_read, + .write = ata_write, + .multiplier = 8 +}; + +static struct device_driver atapi_driver = { + .name = "atapi", + .probe = atapi_probe, + .read = atapi_read, + .read_nonblock = atapi_read, +}; + +void ata_init() +{ + int i; + + int nblocks; + int blocksize = 0; + + char longname[256]; + for (int i = 0; i < 4; i++) { + counters.blocks_read[i] = 0; + counters.blocks_written[i] = 0; + } + + printf("ata: setting up interrupts\n"); + + interrupt_register(ATA_IRQ0, ata_interrupt); + interrupt_enable(ATA_IRQ0); + + interrupt_register(ATA_IRQ1, ata_interrupt); + interrupt_enable(ATA_IRQ1); + + printf("ata: probing devices\n"); + + for(i = 0; i < 4; i++) { + ata_probe_internal(i, 0, &nblocks, &blocksize, longname); + } + + device_driver_register(&ata_driver); + device_driver_register(&atapi_driver); +}