Add ATAPI support to ata(4).

This commit is contained in:
Jonas 'Sortie' Termansen 2018-04-27 17:24:59 +02:00
parent bce37028f5
commit 80f5ca398a
6 changed files with 222 additions and 66 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. * Copyright (c) 2013, 2014, 2015, 2016, 2018 Jonas 'Sortie' Termansen.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -92,7 +92,11 @@ static void InitializeDevice(Ref<Descriptor> dev, const char* devpath,
{ {
HBA* hba = new HBA(devaddr); HBA* hba = new HBA(devaddr);
if ( !hba ) if ( !hba )
PanicF("Failed to allocate ATA driver for AHCI device 0x%X", devaddr); {
Log::PrintF("ahci: pci 0x%X: "
"failed to allocate driver object", devaddr);
return;
}
if ( !hba->Initialize(dev, devpath) ) if ( !hba->Initialize(dev, devpath) )
return delete hba; return delete hba;

View File

@ -314,8 +314,6 @@ bool Port::FinishInitialize()
(uint64_t) words[118] << 16); (uint64_t) words[118] << 16);
} }
// TODO: Verify the block size is a power of two.
cylinder_count = words[1]; cylinder_count = words[1];
head_count = words[3]; head_count = words[3];
sector_count = words[6]; sector_count = words[6];

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. * Copyright (c) 2011-2016, 2018 Jonas 'Sortie' Termansen.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -37,7 +37,10 @@ static void InitializeDevice(Ref<Descriptor> dev, const char* devpath,
{ {
HBA* hba = new HBA(devaddr); HBA* hba = new HBA(devaddr);
if ( !hba ) if ( !hba )
PanicF("Failed to allocate ATA driver for PCI device 0x%X", devaddr); {
Log::PrintF("ata: pci 0x%X: failed to allocate driver object", devaddr);
return;
}
if ( !hba->Initialize(dev, devpath) ) if ( !hba->Initialize(dev, devpath) )
return delete hba; return delete hba;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. * Copyright (c) 2011-2016, 2018, 2021 Jonas 'Sortie' Termansen.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -170,7 +170,11 @@ bool Port::FinishInitialize()
channel->SelectDrive(port_index); channel->SelectDrive(port_index);
outport8(channel->port_base + REG_COMMAND, CMD_IDENTIFY); // ATAPI can be detected by using the LBA registers after IDENTIFY.
is_packet_interface = false;
retry_identify_packet:
outport8(channel->port_base + REG_COMMAND,
is_packet_interface ? CMD_IDENTIFY_PACKET : CMD_IDENTIFY);
sleep_400_nanoseconds(); sleep_400_nanoseconds();
@ -211,9 +215,13 @@ bool Port::FinishInitialize()
if ( (mid == 0x14 && high == 0xEB) || (mid == 0x69 && high == 0x96) ) if ( (mid == 0x14 && high == 0xEB) || (mid == 0x69 && high == 0x96) )
{ {
// TODO: Add ATAPI support. if ( is_packet_interface )
//LogF("ignoring: found ATAPI device instead"); {
return errno = ENODRV, false; LogF("ignoring: IDENTIFY_PACKET returned error status");
return errno = EIO, false;
}
is_packet_interface = true;
goto retry_identify_packet;
} }
else if ( mid == 0x3C && high == 0xC3 ) else if ( mid == 0x3C && high == 0xC3 )
{ {
@ -248,46 +256,15 @@ bool Port::FinishInitialize()
little_uint16_t* words = (little_uint16_t*) identify_data; little_uint16_t* words = (little_uint16_t*) identify_data;
if ( words[0] & (1 << 15) ) if ( !is_packet_interface && (words[0] & (1 << 15)) )
return errno = EINVAL, false; // Skipping non-ATA device. return errno = EINVAL, false; // Skipping non-ATA device.
if ( !(words[49] & (1 << 9)) ) if ( !is_packet_interface && !(words[49] & (1 << 9)) )
return errno = EINVAL, false; // Skipping non-LBA device. return errno = EINVAL, false; // Skipping non-LBA device.
this->is_lba48 = words[83] & (1 << 10);
copy_ata_string(serial, (const char*) &words[10], sizeof(serial) - 1); copy_ata_string(serial, (const char*) &words[10], sizeof(serial) - 1);
copy_ata_string(revision, (const char*) &words[23], sizeof(revision) - 1); copy_ata_string(revision, (const char*) &words[23], sizeof(revision) - 1);
copy_ata_string(model, (const char*) &words[27], sizeof(model) - 1); copy_ata_string(model, (const char*) &words[27], sizeof(model) - 1);
uint64_t block_count;
if ( is_lba48 )
{
block_count = (uint64_t) words[100] << 0 |
(uint64_t) words[101] << 16 |
(uint64_t) words[102] << 32 |
(uint64_t) words[103] << 48;
}
else
{
block_count = (uint64_t) words[60] << 0 |
(uint64_t) words[61] << 16;
}
uint64_t block_size = 512;
if( (words[106] & (1 << 14)) &&
!(words[106] & (1 << 15)) &&
(words[106] & (1 << 12)) )
{
block_size = 2 * ((uint64_t) words[117] << 0 |
(uint64_t) words[118] << 16);
}
// TODO: Verify the block size is a power of two.
cylinder_count = words[1];
head_count = words[3];
sector_count = words[6];
is_using_dma = true; is_using_dma = true;
// This is apparently the case on older hardware, there are additional // This is apparently the case on older hardware, there are additional
@ -319,7 +296,57 @@ bool Port::FinishInitialize()
} }
#endif #endif
if ( __builtin_mul_overflow(block_count, block_size, &this->device_size) ) if ( is_packet_interface )
{
is_lba48 = true;
block_count = 0;
block_size = 0;
cylinder_count = 0;
head_count = 0;
sector_count = 0;
device_size = 0;
if ( !ReadCapacityATAPI(true) )
{
//LogF("ReadCapacityATAPI failed: %m");
}
return true;
}
this->is_lba48 = words[83] & (1 << 10);
uint64_t block_count;
if ( is_lba48 )
{
block_count = (uint64_t) words[100] << 0 |
(uint64_t) words[101] << 16 |
(uint64_t) words[102] << 32 |
(uint64_t) words[103] << 48;
}
else
{
block_count = (uint64_t) words[60] << 0 |
(uint64_t) words[61] << 16;
}
uint64_t block_size = 512;
if( (words[106] & (1 << 14)) &&
!(words[106] & (1 << 15)) &&
(words[106] & (1 << 12)) )
{
block_size = 2 * ((uint64_t) words[117] << 0 |
(uint64_t) words[118] << 16);
}
cylinder_count = words[1];
head_count = words[3];
sector_count = words[6];
if ( Page::Size() < block_size )
{
LogF("error: block size is larger than page size: %ji", block_size);
return errno = EINVAL, false;
}
if ( __builtin_mul_overflow(block_count, block_size, &device_size) )
{ {
LogF("error: device size overflows off_t"); LogF("error: device size overflows off_t");
return errno = EOVERFLOW, false; return errno = EOVERFLOW, false;
@ -331,6 +358,67 @@ bool Port::FinishInitialize()
return true; return true;
} }
bool Port::ReadCapacityATAPI(bool no_error)
{
struct atapi_packet* packet = (struct atapi_packet*) dma_alloc.from;
memset(packet, 0, sizeof(*packet));
packet->operation = ATAPI_CMD_READ_CAPACITY;
struct atapi_capacity* reply = (struct atapi_capacity*) dma_alloc.from;
if ( !CommandATAPI(sizeof(*reply), no_error) )
return errno = ENOMEDIUM, false;
block_count = (blkcnt_t) reply->last_lba + 1;
block_size = (blkcnt_t) reply->block_size;
if ( Page::Size() < (uintmax_t) block_size )
{
LogF("error: block size is larger than page size: %ji", block_size);
return errno = EINVAL, false;
}
if ( __builtin_mul_overflow(block_count, block_size, &device_size) )
{
LogF("error: device size overflows off_t");
return errno = EOVERFLOW, false;
}
return true;
}
bool Port::CommandATAPI(size_t response_size, bool no_error)
{
outport8(channel->port_base + REG_FEATURE, is_using_dma ? 0x01 : 0x00);
outport8(channel->port_base + REG_LBA_LOW, 0x08);
outport8(channel->port_base + REG_LBA_MID, 0x08);
if ( is_using_dma )
CommandDMA(CMD_PACKET, response_size, false);
else
CommandPIO(CMD_PACKET, response_size, false);
if ( !TransferPIO(sizeof(struct atapi_packet), true, no_error) )
{
if ( !no_error )
LogF("EIO: %s:%u", __FILE__, __LINE__);
return errno = EIO, false;
}
if ( is_using_dma )
{
if ( !FinishTransferDMA() )
{
if ( !no_error )
LogF("EIO: %s:%u", __FILE__, __LINE__);
return errno = EIO, false;
}
}
else
{
// TODO: Read LBA Mid and LBA High to know how many bytes we are
// expected to transfer.
if ( !TransferPIO(response_size, false, no_error) )
{
if ( !no_error )
LogF("EIO: %s:%u", __FILE__, __LINE__);
return errno = EIO, false;
}
}
return true;
}
void Port::Seek(blkcnt_t block_index, size_t count) void Port::Seek(blkcnt_t block_index, size_t count)
{ {
uintmax_t lba = (uintmax_t) block_index; uintmax_t lba = (uintmax_t) block_index;
@ -449,7 +537,7 @@ void Port::CommandPIO(uint8_t cmd, size_t size, bool write)
outport8(channel->port_base + REG_COMMAND, cmd); outport8(channel->port_base + REG_COMMAND, cmd);
} }
bool Port::TransferPIO(size_t size, bool write) bool Port::TransferPIO(size_t size, bool write, bool no_error)
{ {
const char* op = write ? "write" : "read"; const char* op = write ? "write" : "read";
size_t i = 0; size_t i = 0;
@ -457,14 +545,16 @@ bool Port::TransferPIO(size_t size, bool write)
{ {
if ( !write && i < size && !AwaitInterrupt(10000 /*ms*/) ) if ( !write && i < size && !AwaitInterrupt(10000 /*ms*/) )
{ {
LogF("error: %s timed out, waiting for transfer start", op); if ( !no_error )
LogF("error: %s timed out, waiting for transfer start", op);
return errno = EIO, false; return errno = EIO, false;
} }
if ( !wait_inport8_clear(channel->port_base + REG_STATUS, if ( !wait_inport8_clear(channel->port_base + REG_STATUS,
STATUS_BUSY, false, 10000 /*ms*/) ) STATUS_BUSY, false, 10000 /*ms*/) )
{ {
LogF("error: %s timed out waiting for transfer pre idle", op); if ( !no_error )
LogF("error: %s timed out waiting for transfer pre idle", op);
return errno = EIO, false; return errno = EIO, false;
} }
@ -472,13 +562,17 @@ bool Port::TransferPIO(size_t size, bool write)
if ( status & STATUS_BUSY ) if ( status & STATUS_BUSY )
{ {
LogF("error: %s unexpectedly still busy", op); if ( !no_error )
LogF("error: %s unexpectedly still busy", op);
return errno = EIO, false; return errno = EIO, false;
} }
if ( status & (STATUS_ERROR | STATUS_DRIVEFAULT) ) if ( status & (STATUS_ERROR | STATUS_DRIVEFAULT) )
{ {
LogF("error: %s error", op); if ( !no_error )
LogF("error: %s error%s%s", op,
status & STATUS_ERROR ? " STATUS_ERROR" : "",
status & STATUS_DRIVEFAULT ? " STATUS_DRIVEFAULT" : "");
return errno = EIO, false; return errno = EIO, false;
} }
@ -487,13 +581,14 @@ bool Port::TransferPIO(size_t size, bool write)
if ( !(status & STATUS_DATAREADY) ) if ( !(status & STATUS_DATAREADY) )
{ {
LogF("error: %s unexpectedly not ready", op); if ( !no_error )
LogF("error: %s unexpectedly not ready", op);
return errno = EIO, false; return errno = EIO, false;
} }
// Anticipate another IRQ if we're not at the end. // Anticipate another IRQ if we're not at the end.
size_t i_sector_end = i + block_size; size_t i_sector_end = is_packet_interface ? i + size : i + block_size;
if ( i_sector_end != size ) if ( (is_packet_interface && write) || i_sector_end != size )
PrepareAwaitInterrupt(); PrepareAwaitInterrupt();
uint8_t* dma_data = (uint8_t*) dma_alloc.from; uint8_t* dma_data = (uint8_t*) dma_alloc.from;
@ -520,7 +615,8 @@ bool Port::TransferPIO(size_t size, bool write)
if ( write && !AwaitInterrupt(10000 /*ms*/) ) if ( write && !AwaitInterrupt(10000 /*ms*/) )
{ {
LogF("error: %s timed out, waiting for transfer end", op); if ( !no_error )
LogF("error: %s timed out, waiting for transfer end", op);
return errno = EIO, false; return errno = EIO, false;
} }
} }
@ -585,6 +681,11 @@ const unsigned char* Port::GetATAIdentify(size_t* size_ptr)
int Port::sync(ioctx_t* ctx) int Port::sync(ioctx_t* ctx)
{ {
if ( is_packet_interface )
{
// TODO: SYNCHRONIZE CACHE 0x35
return errno = ENOSYS, -1;
}
(void) ctx; (void) ctx;
ScopedLock lock(&channel->hw_lock); ScopedLock lock(&channel->hw_lock);
channel->SelectDrive(port_index); channel->SelectDrive(port_index);
@ -613,6 +714,8 @@ int Port::sync(ioctx_t* ctx)
ssize_t Port::pread(ioctx_t* ctx, unsigned char* buf, size_t count, off_t off) ssize_t Port::pread(ioctx_t* ctx, unsigned char* buf, size_t count, off_t off)
{ {
if ( !block_size )
return errno = ENOMEDIUM, -1;
ssize_t result = 0; ssize_t result = 0;
while ( count ) while ( count )
{ {
@ -635,20 +738,36 @@ ssize_t Port::pread(ioctx_t* ctx, unsigned char* buf, size_t count, off_t off)
unsigned char* dma_data = (unsigned char*) dma_alloc.from; unsigned char* dma_data = (unsigned char*) dma_alloc.from;
unsigned char* data = dma_data + block_offset; unsigned char* data = dma_data + block_offset;
size_t data_size = amount - block_offset; size_t data_size = amount - block_offset;
Seek(block_index, num_blocks); if ( is_packet_interface )
if ( is_using_dma )
{ {
uint8_t cmd = is_lba48 ? CMD_READ_DMA_EXT : CMD_READ_DMA; struct atapi_packet* packet = (struct atapi_packet*) dma_alloc.from;
CommandDMA(cmd, (size_t) full_amount, false); memset(packet, 0, sizeof(*packet));
if ( !FinishTransferDMA() ) packet->operation = ATAPI_CMD_READ;
packet->lba[0] = block_index >> 24 & 0xFF;
packet->lba[1] = block_index >> 16 & 0xFF;
packet->lba[2] = block_index >> 8 & 0xFF;
packet->lba[3] = block_index >> 0 & 0xFF;
packet->control = num_blocks;
if ( !CommandATAPI((size_t) full_amount) )
return result ? result : -1; return result ? result : -1;
} }
else else
{ {
uint8_t cmd = is_lba48 ? CMD_READ_EXT : CMD_READ; Seek(block_index, num_blocks);
CommandPIO(cmd, (size_t) full_amount, false); if ( is_using_dma )
if ( !TransferPIO((size_t) full_amount, false) ) {
return result ? result : -1; uint8_t cmd = is_lba48 ? CMD_READ_DMA_EXT : CMD_READ_DMA;
CommandDMA(cmd, (size_t) full_amount, false);
if ( !FinishTransferDMA() )
return result ? result : -1;
}
else
{
uint8_t cmd = is_lba48 ? CMD_READ_EXT : CMD_READ;
CommandPIO(cmd, (size_t) full_amount, false);
if ( !TransferPIO((size_t) full_amount, false) )
return result ? result : -1;
}
} }
if ( !ctx->copy_to_dest(buf, data, data_size) ) if ( !ctx->copy_to_dest(buf, data, data_size) )
return result ? result : -1; return result ? result : -1;
@ -662,6 +781,10 @@ ssize_t Port::pread(ioctx_t* ctx, unsigned char* buf, size_t count, off_t off)
ssize_t Port::pwrite(ioctx_t* ctx, const unsigned char* buf, size_t count, off_t off) ssize_t Port::pwrite(ioctx_t* ctx, const unsigned char* buf, size_t count, off_t off)
{ {
if ( is_packet_interface )
return errno = EPERM, -1;
if ( !block_size )
return errno = ENOMEDIUM, -1;
ssize_t result = 0; ssize_t result = 0;
while ( count ) while ( count )
{ {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. * Copyright (c) 2011-2016, 2018, 2021 Jonas 'Sortie' Termansen.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -65,11 +65,13 @@ public:
private: private:
__attribute__((format(printf, 2, 3))) __attribute__((format(printf, 2, 3)))
void LogF(const char* format, ...); void LogF(const char* format, ...);
bool ReadCapacityATAPI(bool no_error = false);
void Seek(blkcnt_t block_index, size_t count); void Seek(blkcnt_t block_index, size_t count);
bool CommandATAPI(size_t response_size, bool no_error = false);
void CommandDMA(uint8_t cmd, size_t size, bool write); void CommandDMA(uint8_t cmd, size_t size, bool write);
void CommandPIO(uint8_t cmd, size_t size, bool write); void CommandPIO(uint8_t cmd, size_t size, bool write);
bool FinishTransferDMA(); bool FinishTransferDMA();
bool TransferPIO(size_t size, bool write); bool TransferPIO(size_t size, bool write, bool no_error = false);
void PrepareAwaitInterrupt(); void PrepareAwaitInterrupt();
bool AwaitInterrupt(unsigned int msescs); bool AwaitInterrupt(unsigned int msescs);
void OnInterrupt(); void OnInterrupt();
@ -90,6 +92,7 @@ private:
bool is_dma_page_mapped; bool is_dma_page_mapped;
bool is_lba48; bool is_lba48;
bool is_using_dma; bool is_using_dma;
bool is_packet_interface;
off_t device_size; off_t device_size;
blksize_t block_count; blksize_t block_count;
blkcnt_t block_size; blkcnt_t block_size;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. * Copyright (c) 2011-2016, 2018, 2021 Jonas 'Sortie' Termansen.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -33,6 +33,25 @@ struct prd
little_uint16_t flags; little_uint16_t flags;
}; };
struct atapi_packet
{
uint8_t operation;
uint8_t reladdr_lun;
uint8_t lba[4];
uint8_t reserved0;
uint8_t reserved1;
uint8_t pmi;
uint8_t control;
uint8_t reserved2;
uint8_t reserved3;
};
struct atapi_capacity
{
big_uint32_t last_lba;
big_uint32_t block_size;
};
static const uint16_t PRD_FLAG_EOT = 1 << 15; static const uint16_t PRD_FLAG_EOT = 1 << 15;
static const uint16_t REG_DATA = 0x0; static const uint16_t REG_DATA = 0x0;
@ -57,6 +76,12 @@ static const uint8_t CMD_WRITE_DMA_EXT = 0x35;
static const uint8_t CMD_FLUSH_CACHE = 0xE7; static const uint8_t CMD_FLUSH_CACHE = 0xE7;
static const uint8_t CMD_FLUSH_CACHE_EXT = 0xEA; static const uint8_t CMD_FLUSH_CACHE_EXT = 0xEA;
static const uint8_t CMD_IDENTIFY = 0xEC; static const uint8_t CMD_IDENTIFY = 0xEC;
static const uint8_t CMD_PACKET = 0xA0;
static const uint8_t CMD_IDENTIFY_PACKET = 0xA1;
static const uint8_t ATAPI_CMD_READ_CAPACITY = 0x25;
static const uint8_t ATAPI_CMD_WRITE_CAPACITY = 0x28;
static const uint8_t ATAPI_CMD_READ = 0xA8;
static const uint8_t STATUS_ERROR = 1 << 0; static const uint8_t STATUS_ERROR = 1 << 0;
static const uint8_t STATUS_DATAREADY = 1 << 3; static const uint8_t STATUS_DATAREADY = 1 << 3;