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
* 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);
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) )
return delete hba;

View File

@ -314,8 +314,6 @@ bool Port::FinishInitialize()
(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];

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
* 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);
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) )
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
* purpose with or without fee is hereby granted, provided that the above
@ -170,7 +170,11 @@ bool Port::FinishInitialize()
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();
@ -211,9 +215,13 @@ bool Port::FinishInitialize()
if ( (mid == 0x14 && high == 0xEB) || (mid == 0x69 && high == 0x96) )
{
// TODO: Add ATAPI support.
//LogF("ignoring: found ATAPI device instead");
return errno = ENODRV, false;
if ( is_packet_interface )
{
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 )
{
@ -248,46 +256,15 @@ bool Port::FinishInitialize()
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.
if ( !(words[49] & (1 << 9)) )
if ( !is_packet_interface && !(words[49] & (1 << 9)) )
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(revision, (const char*) &words[23], sizeof(revision) - 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;
// This is apparently the case on older hardware, there are additional
@ -319,7 +296,57 @@ bool Port::FinishInitialize()
}
#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");
return errno = EOVERFLOW, false;
@ -331,6 +358,67 @@ bool Port::FinishInitialize()
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)
{
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);
}
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";
size_t i = 0;
@ -457,14 +545,16 @@ bool Port::TransferPIO(size_t size, bool write)
{
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;
}
if ( !wait_inport8_clear(channel->port_base + REG_STATUS,
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;
}
@ -472,13 +562,17 @@ bool Port::TransferPIO(size_t size, bool write)
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;
}
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;
}
@ -487,13 +581,14 @@ bool Port::TransferPIO(size_t size, bool write)
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;
}
// Anticipate another IRQ if we're not at the end.
size_t i_sector_end = i + block_size;
if ( i_sector_end != size )
size_t i_sector_end = is_packet_interface ? i + size : i + block_size;
if ( (is_packet_interface && write) || i_sector_end != size )
PrepareAwaitInterrupt();
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*/) )
{
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;
}
}
@ -585,6 +681,11 @@ const unsigned char* Port::GetATAIdentify(size_t* size_ptr)
int Port::sync(ioctx_t* ctx)
{
if ( is_packet_interface )
{
// TODO: SYNCHRONIZE CACHE 0x35
return errno = ENOSYS, -1;
}
(void) ctx;
ScopedLock lock(&channel->hw_lock);
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)
{
if ( !block_size )
return errno = ENOMEDIUM, -1;
ssize_t result = 0;
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* data = dma_data + block_offset;
size_t data_size = amount - block_offset;
Seek(block_index, num_blocks);
if ( is_using_dma )
if ( is_packet_interface )
{
uint8_t cmd = is_lba48 ? CMD_READ_DMA_EXT : CMD_READ_DMA;
CommandDMA(cmd, (size_t) full_amount, false);
if ( !FinishTransferDMA() )
struct atapi_packet* packet = (struct atapi_packet*) dma_alloc.from;
memset(packet, 0, sizeof(*packet));
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;
}
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;
Seek(block_index, num_blocks);
if ( is_using_dma )
{
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) )
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)
{
if ( is_packet_interface )
return errno = EPERM, -1;
if ( !block_size )
return errno = ENOMEDIUM, -1;
ssize_t result = 0;
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
* purpose with or without fee is hereby granted, provided that the above
@ -65,11 +65,13 @@ public:
private:
__attribute__((format(printf, 2, 3)))
void LogF(const char* format, ...);
bool ReadCapacityATAPI(bool no_error = false);
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 CommandPIO(uint8_t cmd, size_t size, bool write);
bool FinishTransferDMA();
bool TransferPIO(size_t size, bool write);
bool TransferPIO(size_t size, bool write, bool no_error = false);
void PrepareAwaitInterrupt();
bool AwaitInterrupt(unsigned int msescs);
void OnInterrupt();
@ -90,6 +92,7 @@ private:
bool is_dma_page_mapped;
bool is_lba48;
bool is_using_dma;
bool is_packet_interface;
off_t device_size;
blksize_t block_count;
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
* purpose with or without fee is hereby granted, provided that the above
@ -33,6 +33,25 @@ struct prd
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 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_EXT = 0xEA;
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_DATAREADY = 1 << 3;