atapicdrom: Implement sector areas for Read CD.

The disk cache is unchanged. data_ptr continues to be only used for the user data sector area for each block. The other sector areas (synch, header, etc.) are filled in while reading.

has_data and get_data exist as a way to bypass data_ptr for parts of the transfer outside the user data sector area of each block. The default behaviour is defined in atabasedevice and is overridden by atapicdrom for the Read CD command. atapicdrom has a flag doing_sector_areas to control the behavior of the get_data method. When the flag is true, the sector_areas, current_block, and current_block_byte are used for selecting the correct data from one of the sector areas. The Read CD command initializes those variables. xfer_cnt remains the total number of bytes to be transferred and is now not necessarily the same as the number of disk image blocks read into the disk cache.

lba_to_msf is used to fill in the header. The values was not verified using a real CD.

Mac OS X just cares about the Mode in the header. For now, only the synch and header and user data areas are filled in. The other areas read as all zeros.
This commit is contained in:
joevt 2023-08-20 21:02:02 -07:00 committed by Maxim Poliakovski
parent ec5bf8e985
commit 637844269f
5 changed files with 179 additions and 21 deletions

View File

@ -27,6 +27,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/ata/atadefs.h>
#include <devices/common/ata/idechannel.h>
#include <devices/common/hwcomponent.h>
#include "endianswap.h"
#include <cinttypes>
#include <string>
@ -55,6 +56,14 @@ public:
void device_control(const uint8_t new_ctrl);
void update_intrq(uint8_t new_intrq_state);
bool has_data() {
return data_ptr && xfer_cnt;
}
uint16_t get_data() {
return BYTESWAP_16(*this->data_ptr++);
}
protected:
bool is_selected() { return ((this->r_dev_head >> 4) & 1) == this->my_dev_id; };

View File

@ -50,28 +50,26 @@ void AtapiBaseDevice::device_set_signature() {
uint16_t AtapiBaseDevice::read(const uint8_t reg_addr) {
switch (reg_addr) {
case ATA_Reg::DATA:
if (this->data_ptr && this->xfer_cnt) {
if (has_data()) {
uint16_t ret_data = get_data();
this->xfer_cnt -= 2;
if (this->xfer_cnt <= 0) {
this->r_status &= ~DRQ;
if ((this->r_int_reason & ATAPI_Int_Reason::IO) &&
!(this->r_int_reason & ATAPI_Int_Reason::CoD)) {
uint16_t ret_data = BYTESWAP_16(*this->data_ptr++);
!(this->r_int_reason & ATAPI_Int_Reason::CoD)
) {
if (this->data_available()) {
this->r_status &= ~DRQ;
this->r_status |= BSY;
this->update_intrq(1);
return ret_data;
this->update_intrq(1); // Is this going to work here? The interrupt happens before returning ret_data.
}
if (this->status_expected) {
else if (this->status_expected) {
this->present_status();
}
return ret_data;
}
}
return BYTESWAP_16(*this->data_ptr++);
return ret_data;
} else {
return 0xFFFFU;
}
@ -105,7 +103,7 @@ uint16_t AtapiBaseDevice::read(const uint8_t reg_addr) {
void AtapiBaseDevice::write(const uint8_t reg_addr, const uint16_t value) {
switch (reg_addr) {
case ATA_Reg::DATA:
if (this->data_ptr && this->xfer_cnt) {
if (has_data()) {
*this->data_ptr++ = BYTESWAP_16(value);
this->xfer_cnt -= 2;
if (this->xfer_cnt <= 0) {

View File

@ -68,6 +68,11 @@ void AtapiCdrom::perform_packet_command() {
uint32_t lba, xfer_len;
this->r_status |= BSY;
this->sector_areas = 0;
if (this->doing_sector_areas) {
this->doing_sector_areas = false;
LOG_F(WARNING, "%s: doing_sector_areas reset", this->name.c_str());
}
switch (this->cmd_pkt[0]) {
case ScsiCommand::TEST_UNIT_READY:
@ -183,20 +188,70 @@ void AtapiCdrom::perform_packet_command() {
this->present_status();
break;
case ScsiCommand::READ_CD:
lba = READ_DWORD_BE_U(&this->cmd_pkt[2]);
{
lba = READ_DWORD_BE_U(&this->cmd_pkt[2]);
xfer_len = (this->cmd_pkt[6] << 16) | READ_WORD_BE_U(&this->cmd_pkt[7]);
if (this->cmd_pkt[1] || this->cmd_pkt[9] != 0x10 || this->cmd_pkt[10])
LOG_F(WARNING, "%s: unsupported READ_CD params", this->name.c_str());
if (this->cmd_pkt[1] || (this->cmd_pkt[9] & ~0xf8) || ((this->cmd_pkt[9] & 0xf8) == 0) || this->cmd_pkt[10])
LOG_F(WARNING, "%s: unsupported READ_CD params: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
this->name.c_str(),
this->cmd_pkt[0], this->cmd_pkt[1], this->cmd_pkt[2], this->cmd_pkt[3], this->cmd_pkt[4], this->cmd_pkt[5],
this->cmd_pkt[6], this->cmd_pkt[7], this->cmd_pkt[8], this->cmd_pkt[9], this->cmd_pkt[10], this->cmd_pkt[11]
);
if (this->r_features & ATAPI_Features::DMA) {
LOG_F(WARNING, "ATAPI DMA transfer requsted");
}
this->set_fpos(lba);
this->xfer_cnt = this->read_begin(xfer_len, this->r_byte_count);
this->r_byte_count = this->xfer_cnt;
this->sector_areas = cmd_pkt[9];
this->doing_sector_areas = true;
this->current_block = lba;
this->current_block_byte = 0;
int bytes_prolog = 0;
int bytes_data = 0;
int bytes_epilog = 0;
// For Mode 1 CD-ROMs:
if (this->sector_areas & 0x80) bytes_prolog += 12; // Sync
if (this->sector_areas & 0x20) bytes_prolog += 4; // Header
if (this->sector_areas & 0x40) bytes_prolog += 0; // SubHeader
if (this->sector_areas & 0x10) bytes_data += 2048; // User
if (this->sector_areas & 0x08) bytes_epilog += 288; // Auxiliary
if (this->sector_areas & 0x02) bytes_epilog += 294; // ErrorFlags
if (this->sector_areas & 0x01) bytes_epilog += 96; // SubChannel
int bytes_per_block = bytes_prolog + bytes_data + bytes_epilog;
int disk_image_byte_count;
if (bytes_per_block == 0) {
disk_image_byte_count = 0xffff;
}
else {
disk_image_byte_count = (this->r_byte_count / bytes_per_block) * this->block_size; // whole blocks
if ((this->r_byte_count % bytes_per_block) > bytes_prolog) { // partial block
int disk_image_byte_count_partial_block = (this->r_byte_count % bytes_per_block) - bytes_prolog; // remove prolog from partial block
if (disk_image_byte_count_partial_block > this->block_size) { // partial block includes some epilog?
disk_image_byte_count_partial_block = this->block_size; // // remove epilog from partial block
}
// add partial block
disk_image_byte_count += disk_image_byte_count_partial_block;
}
}
int disk_image_bytes_received = this->read_begin(xfer_len, disk_image_byte_count);
int bytes_received = (disk_image_bytes_received / this->block_size) * bytes_per_block + bytes_prolog + (disk_image_bytes_received % this->block_size); // whole blocks + prolog + partial block
if (bytes_received > this->r_byte_count) { // if partial epilog or partial prolog
bytes_received = this->r_byte_count; // confine to r_byte_count
}
this->r_byte_count = bytes_received;
this->xfer_cnt = bytes_received;
this->data_ptr = (uint16_t*)this->data_cache.get();
this->status_good();
this->data_out_phase();
break;
}
default:
LOG_F(ERROR, "%s: unsupported ATAPI command 0x%X", this->name.c_str(),
this->cmd_pkt[0]);
@ -205,6 +260,101 @@ void AtapiCdrom::perform_packet_command() {
}
}
int AtapiCdrom::request_data() {
// continuation of READ_CD above
this->data_ptr = (uint16_t*)this->data_cache.get();
this->xfer_cnt = this->read_more();
return this->xfer_cnt;
}
static const uint8_t mode_1_sync[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
uint16_t AtapiCdrom::get_data() {
uint16_t ret_data;
if (doing_sector_areas) {
// For Mode 1 CD-ROMs:
int area_start;
int area_end = 0;
ret_data = 0xffff;
if (this->sector_areas & 0x80) {
area_start = area_end;
area_end += 12; // Sync
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = BYTESWAP_16(*((uint16_t*)(&mode_1_sync[current_block_byte - area_start])));
}
}
if (this->sector_areas & 0x20) {
area_start = area_end;
area_end += 4; // Header
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
AddrMsf msf = lba_to_msf(this->current_block + 150);
uint8_t header[4];
header[0] = msf.min;
header[1] = msf.sec;
header[2] = msf.frm;
header[3] = 0x01; // Mode 1
ret_data = BYTESWAP_16(*((uint16_t*)(&header[current_block_byte - area_start])));
}
}
if (this->sector_areas & 0x40) {
area_start = area_end;
area_end += 0; // SubHeader
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = 0;
}
}
if (this->sector_areas & 0x10) {
area_start = area_end;
area_end += 2048; // User
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = AtaBaseDevice::get_data();
}
}
if (this->sector_areas & 0x08) {
area_start = area_end;
area_end += 288; // Auxiliary
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = 0;
}
}
if (this->sector_areas & 0x02) {
area_start = area_end;
area_end += 294; // ErrorFlags
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = 0;
}
}
if (this->sector_areas & 0x01) {
area_start = area_end;
area_end += 96; // SubChannel
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = 0;
}
}
current_block_byte += 2;
if (current_block_byte >= area_end)
current_block_byte = 0;
}
else {
ret_data = AtaBaseDevice::get_data();
}
return ret_data;
}
void AtapiCdrom::status_good() {
this->status_expected = true;
this->r_error = 0;

View File

@ -42,11 +42,7 @@ public:
void perform_packet_command() override;
int request_data() override {
this->data_ptr = (uint16_t*)this->data_cache.get();
this->xfer_cnt = this->read_more();
return this->xfer_cnt;
}
int request_data() override;
bool data_available() override {
return this->data_left() != 0;
@ -55,10 +51,16 @@ public:
void status_good();
void status_error(uint8_t sense_key, uint8_t asc);
uint16_t get_data();
private:
uint8_t sense_key = 0;
uint8_t asc = 0;
uint8_t ascq = 0;
bool doing_sector_areas = false;
uint8_t sector_areas;
uint32_t current_block;
uint16_t current_block_byte;
};
#endif // ATAPI_CDROM_H

View File

@ -67,7 +67,6 @@ public:
uint32_t report_capacity(uint8_t *data_ptr);
uint32_t read_toc(uint8_t *cmd_ptr, uint8_t *data_ptr);
private:
AddrMsf lba_to_msf(const int lba);
protected: