Skip to content

Commit 52f4d84

Browse files
committed
Fixes for READ_MULTIPLE and WRITE_MULTIPLE commands
We may be transferring less data than can fit in a chunk, so we need to ensure that xfer_cnt is clamped to 0 when transferring the last chunk (otherwise it remains negative, and has_data() will return true). It's also possible that the transfer size is bigger than a chunk but not an even multiple of the chunk size, so we need to ensure that we don't try to transfer a whole chunk in the last iteration. More correctly initialize the device identification struct, to report the maximum (word 47) and current (word 59) number of blocks that can be transferred with READ_MULTIPLE and WRITE_MULTIPLE commands. Fix post_xfer_action to write the actual data size that was written, as opposed to an entire chunk (which may be larger).
1 parent ebf4b0e commit 52f4d84

File tree

3 files changed

+28
-11
lines changed

3 files changed

+28
-11
lines changed

devices/common/ata/atabasedevice.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,11 @@ uint16_t AtaBaseDevice::read(const uint8_t reg_addr) {
7070
this->chunk_cnt -= 2;
7171
if (this->chunk_cnt <= 0) {
7272
this->xfer_cnt -= this->chunk_size;
73-
if (this->xfer_cnt <= 0)
73+
if (this->xfer_cnt <= 0) {
74+
this->xfer_cnt = 0;
7475
this->r_status &= ~DRQ;
75-
else {
76-
this->chunk_cnt = this->chunk_size;
76+
} else {
77+
this->chunk_cnt = std::min(this->xfer_cnt, this->chunk_size);
7778
this->update_intrq(1);
7879
}
7980
}
@@ -114,12 +115,13 @@ void AtaBaseDevice::write(const uint8_t reg_addr, const uint16_t value) {
114115
this->post_xfer_action();
115116
this->xfer_cnt -= this->chunk_size;
116117
if (this->xfer_cnt <= 0) { // transfer complete?
118+
this->xfer_cnt = 0;
117119
this->r_status &= ~DRQ;
118120
this->r_status &= ~BSY;
119121
this->update_intrq(1);
120122
} else {
121123
this->cur_data_ptr = this->data_ptr;
122-
this->chunk_cnt = this->chunk_size;
124+
this->chunk_cnt = std::min(this->xfer_cnt, this->chunk_size);
123125
this->signal_data_ready();
124126
}
125127
}

devices/common/ata/atahd.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ int AtaHardDisk::perform_command() {
106106
uint32_t ints_size = ATA_HD_SEC_SIZE;
107107
if (this->r_command == READ_MULTIPLE) {
108108
if (this->sec_per_block == 0) {
109-
LOG_F(ERROR, "%s: READ MULTIPLE with SET MULTIPLE==0", this->name.c_str());
109+
LOG_F(ERROR, "%s: cannot do a READ_MULTIPLE with unset sec_per_block", this->name.c_str());
110110
this->r_status |= ERR;
111111
this->r_status &= ~BSY;
112112
break;
@@ -131,7 +131,7 @@ int AtaHardDisk::perform_command() {
131131
uint32_t ints_size = ATA_HD_SEC_SIZE;
132132
if (this->r_command == WRITE_MULTIPLE) {
133133
if (this->sec_per_block == 0) {
134-
LOG_F(ERROR, "%s: WRITE MULTIPLE with SET MULTIPLE==0", this->name.c_str());
134+
LOG_F(ERROR, "%s: cannot do a WRITE_MULTIPLE with unset sec_per_block", this->name.c_str());
135135
this->r_status |= ERR;
136136
this->r_status &= ~BSY;
137137
break;
@@ -140,8 +140,9 @@ int AtaHardDisk::perform_command() {
140140
}
141141
this->prepare_xfer(xfer_size, ints_size);
142142
this->post_xfer_action = [this]() {
143-
this->hdd_img.write(this->data_ptr, this->cur_fpos, this->chunk_size);
144-
this->cur_fpos += this->chunk_size;
143+
uint64_t write_len = (this->cur_data_ptr - this->data_ptr) * sizeof(this->data_ptr[0]);
144+
this->hdd_img.write(this->data_ptr, this->cur_fpos, write_len);
145+
this->cur_fpos += write_len;
145146
};
146147
this->r_status |= DRQ;
147148
this->r_status &= ~BSY;
@@ -168,10 +169,12 @@ int AtaHardDisk::perform_command() {
168169
case SET_MULTIPLE_MODE: // this command is mandatory for ATA devices
169170
if (!this->r_sect_count || this->r_sect_count > 128 ||
170171
std::bitset<8>(this->r_sect_count).count() != 1) { // power of two?
172+
LOG_F(ERROR, "%s: SET_MULTIPLE_MODE not suported, invalid r_sect_count (%d)", this->name.c_str(), this->r_sect_count);
171173
this->multiple_enabled = false;
172174
this->r_error |= ABRT;
173175
this->r_status |= ERR;
174176
} else {
177+
LOG_F(INFO, "%s: SET_MULTIPLE_MODE, r_sect_count=%d", this->name.c_str(), this->r_sect_count);
175178
this->sec_per_block = this->r_sect_count;
176179
this->multiple_enabled = true;
177180
}
@@ -235,9 +238,21 @@ void AtaHardDisk::prepare_identify_info() {
235238
std::memset(this->data_buf, 0, sizeof(this->data_buf));
236239

237240
buf_ptr[ 0] = 0x0040; // ATA device, non-removable media, non-removable drive
238-
buf_ptr[47] = this->sec_per_block; // block size of READ_MULTIPLE/WRITE_MULTIPLE
239241
buf_ptr[49] = 0x0200; // report LBA support
240242

243+
// Maximum number of logical sectors per data block that the device supports
244+
// for READ_MULTIPLE/WRITE_MULTIPLE commands.
245+
const int max_sec_per_block = 8;
246+
if (max_sec_per_block > 1) {
247+
buf_ptr[47] = 0x8000 | max_sec_per_block;
248+
}
249+
// If bit 8 of word 59 is set to one, then bits 7:0 indicate the number of
250+
// logical sectors that shall be transferred per data block for
251+
// READ_MULTIPLE/WRITE_MULTIPLE commands.
252+
if (this->sec_per_block) {
253+
buf_ptr[59] = 0x100 | this->sec_per_block;
254+
}
255+
241256
buf_ptr[ 1] = this->cylinders;
242257
buf_ptr[ 3] = this->heads;
243258
buf_ptr[ 6] = this->sectors;

devices/common/ata/atahd.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ class AtaHardDisk : public AtaBaseDevice
6969
uint8_t heads;
7070
uint8_t sectors;
7171

72-
uint8_t sec_per_block = 8; // sectors per block for READ_MULTIPLE/WRITE_MULTIPLE
73-
bool multiple_enabled = true; // READ_MULTIPLE/WRITE_MULTIPLE enabled
72+
uint8_t sec_per_block = 0; // sectors per block for READ_MULTIPLE/WRITE_MULTIPLE
73+
bool multiple_enabled = false; // READ_MULTIPLE/WRITE_MULTIPLE enabled
7474

7575
char * buffer = new char[1 <<17];
7676
};

0 commit comments

Comments
 (0)