534 lines
13 KiB
C
534 lines
13 KiB
C
/*
|
|
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);
|
|
}
|