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);
 | |
| }
 |