The Pedigree Project 0.1
ata-pio-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Log.h"
9#include "pedigree/kernel/processor/IoBase.h"
10#include "pedigree/kernel/time/Time.h"
11
12#include "modules/drivers/common/ata/ata-common.h"
13
14namespace {
15class IoEventTrace {
16 public:
17 IoEventTrace()
18 : m_EventCount(0), m_AlternateRun(0), m_PhaseCount(0), m_LastEvent(0), m_Valid(true) {}
19
20 void alternateStatus() {
21 ++m_AlternateRun;
22 if (m_AlternateRun > 4) {
23 m_Valid = false;
24 }
25 record('A');
26 }
27
28 void commandStatus() {
29 if (m_AlternateRun) {
30 m_Valid &= m_AlternateRun == 4;
31 ++m_PhaseCount;
32 } else if (m_LastEvent != 'S') {
33 m_Valid = false;
34 }
35 m_AlternateRun = 0;
36 record('S');
37 }
38
39 void dataWord() {
40 if (m_AlternateRun) {
41 m_Valid = false;
42 }
43 record('W');
44 }
45
46 bool valid(size_t expectedPhases) const {
47 return m_Valid && !m_AlternateRun && m_PhaseCount == expectedPhases;
48 }
49
50 bool finalEventWasStatus() const {
51 return m_EventCount && m_LastEvent == 'S';
52 }
53
54 private:
55 void record(char event) {
56 if (m_EventCount < sizeof(m_Events)) {
57 m_Events[m_EventCount++] = event;
58 } else {
59 m_Valid = false;
60 }
61 m_LastEvent = event;
62 }
63
64 size_t m_EventCount;
65 size_t m_AlternateRun;
66 size_t m_PhaseCount;
67 char m_LastEvent;
68 bool m_Valid;
69 char m_Events[2048];
70};
71
72class ScriptedAtaIo final : public IoBase {
73 public:
74 ScriptedAtaIo(bool command, IoEventTrace* trace, const uint8_t* statuses = nullptr,
75 size_t statusCount = 0, const uint16_t* expectedWords = nullptr,
76 size_t expectedWordCount = 0)
77 : m_Command(command),
78 m_Trace(trace),
79 m_Statuses(statuses),
80 m_StatusCount(statusCount),
81 m_StatusIndex(0),
82 m_ExpectedWords(expectedWords),
83 m_ExpectedWordCount(expectedWordCount),
84 m_StatusReads(0),
85 m_AlternateReads(0),
86 m_DataWrites(0),
87 m_WritesMatch(true),
88 m_UnexpectedAccess(false) {}
89
90 size_t size() const override {
91 return 8;
92 }
93
94 uint8_t read8(size_t offset = 0) override {
95 if (m_Command && offset == 7 && m_StatusCount) {
96 const size_t index = m_StatusIndex < m_StatusCount ? m_StatusIndex++ : m_StatusCount - 1;
97 ++m_StatusReads;
98 m_Trace->commandStatus();
99 return m_Statuses[index];
100 }
101 if (!m_Command && offset == 2) {
102 ++m_AlternateReads;
103 m_Trace->alternateStatus();
104 return 0;
105 }
106
107 m_UnexpectedAccess = true;
108 return 0;
109 }
110
111 uint16_t read16(size_t offset = 0) override {
112 (void)offset;
113 m_UnexpectedAccess = true;
114 return 0;
115 }
116
117 uint32_t read32(size_t offset = 0) override {
118 (void)offset;
119 m_UnexpectedAccess = true;
120 return 0;
121 }
122
123#if BITS_64
124 uint64_t read64(size_t offset = 0) override {
125 (void)offset;
126 m_UnexpectedAccess = true;
127 return 0;
128 }
129#endif
130
131 void write8(uint8_t value, size_t offset = 0) override {
132 (void)value;
133 (void)offset;
134 m_UnexpectedAccess = true;
135 }
136
137 void write16(uint16_t value, size_t offset = 0) override {
138 if (!m_Command || offset || !m_ExpectedWords || m_DataWrites >= m_ExpectedWordCount ||
139 value != m_ExpectedWords[m_DataWrites]) {
140 m_WritesMatch = false;
141 }
142 ++m_DataWrites;
143 m_Trace->dataWord();
144 }
145
146 void write32(uint32_t value, size_t offset = 0) override {
147 (void)value;
148 (void)offset;
149 m_UnexpectedAccess = true;
150 }
151
152#if BITS_64
153 void write64(uint64_t value, size_t offset = 0) override {
154 (void)value;
155 (void)offset;
156 m_UnexpectedAccess = true;
157 }
158#endif
159
160 operator bool() const override {
161 return true;
162 }
163
164 size_t statusReads() const {
165 return m_StatusReads;
166 }
167
168 size_t alternateReads() const {
169 return m_AlternateReads;
170 }
171
172 size_t dataWrites() const {
173 return m_DataWrites;
174 }
175
176 bool writesMatch() const {
177 return m_WritesMatch;
178 }
179
180 bool unexpectedAccess() const {
181 return m_UnexpectedAccess;
182 }
183
184 private:
185 bool m_Command;
186 IoEventTrace* m_Trace;
187 const uint8_t* m_Statuses;
188 size_t m_StatusCount;
189 size_t m_StatusIndex;
190 const uint16_t* m_ExpectedWords;
191 size_t m_ExpectedWordCount;
192 size_t m_StatusReads;
193 size_t m_AlternateReads;
194 size_t m_DataWrites;
195 bool m_WritesMatch;
196 bool m_UnexpectedAccess;
197};
198
199bool check(bool condition, const char* test, const char* detail) {
200 if (condition) {
201 return true;
202 }
203
204 ERROR("HOSTED-WAIT-TEST: FAIL " << test << ": " << detail);
205 return false;
206}
207
208void makeFixtureData(uint16_t* words, size_t count) {
209 for (size_t i = 0; i < count; ++i) {
210 words[i] = static_cast<uint16_t>(0xA500U ^ i);
211 }
212}
213
214AtaPioPollBudget makeBudget(size_t maximumPolls) {
215 AtaPioPollBudget budget = {Time::getTicks(), 30 * Time::Multiplier::Second, 0, maximumPolls};
216 return budget;
217}
218
219bool successfulTwoSectorTransfer() {
220 constexpr const char* Test = "ata-pio-success";
221 const uint8_t statuses[] = {0x48, 0x48, 0x50};
222 uint16_t words[512];
223 makeFixtureData(words, 512);
224 IoEventTrace trace;
225 ScriptedAtaIo command(true, &trace, statuses, 3, words, 512);
226 ScriptedAtaIo control(false, &trace);
227 AtaPioPollBudget budget = makeBudget(3);
228 AtaStatus finalStatus = {};
229
230 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 2, budget, finalStatus);
231 const bool passed = check(
232 result && finalStatus.__reg_contents == 0x50 && budget.polls == 3 &&
233 command.statusReads() == 3 && control.alternateReads() == 12 &&
234 command.dataWrites() == 512 && command.writesMatch() && trace.valid(3) &&
235 trace.finalEventWasStatus() && !command.unexpectedAccess() && !control.unexpectedAccess(),
236 Test, "the helper did not complete two ordered data phases and a terminal status phase");
237 if (passed) {
238 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-success");
239 }
240 return passed;
241}
242
243bool busyProgressesWithinOneBudget() {
244 constexpr const char* Test = "ata-pio-busy-progress";
245 const uint8_t statuses[] = {0x80, 0x48, 0x80, 0x50};
246 uint16_t words[256];
247 makeFixtureData(words, 256);
248 IoEventTrace trace;
249 ScriptedAtaIo command(true, &trace, statuses, 4, words, 256);
250 ScriptedAtaIo control(false, &trace);
251 AtaPioPollBudget budget = makeBudget(4);
252 AtaStatus finalStatus = {};
253
254 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
255 const bool passed =
256 check(result && finalStatus.__reg_contents == 0x50 && budget.polls == 4 &&
257 command.statusReads() == 4 && control.alternateReads() == 8 &&
258 command.dataWrites() == 256 && command.writesMatch() && trace.valid(2) &&
259 trace.finalEventWasStatus(),
260 Test, "BSY progress did not stay within the cumulative data-out budget");
261 if (passed) {
262 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-busy-progress");
263 }
264 return passed;
265}
266
267bool ignoresStaleStatusWhileBusy() {
268 constexpr const char* Test = "ata-pio-busy-stale-status";
269 const uint8_t statuses[] = {0x81, 0xA0, 0x48, 0x50};
270 uint16_t words[256];
271 makeFixtureData(words, 256);
272 IoEventTrace trace;
273 ScriptedAtaIo command(true, &trace, statuses, 4, words, 256);
274 ScriptedAtaIo control(false, &trace);
275 AtaPioPollBudget budget = makeBudget(4);
276 AtaStatus finalStatus = {};
277
278 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
279 const bool passed =
280 check(result && finalStatus.__reg_contents == 0x50 && budget.polls == 4 &&
281 command.statusReads() == 4 && control.alternateReads() == 8 &&
282 command.dataWrites() == 256 && command.writesMatch() && trace.valid(2) &&
283 trace.finalEventWasStatus(),
284 Test, "ERR or device-fault bits were interpreted while BSY made them stale");
285 if (passed) {
286 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-busy-stale-status");
287 }
288 return passed;
289}
290
291bool rejectsLateError() {
292 constexpr const char* Test = "ata-pio-late-error";
293 const uint8_t statuses[] = {0x08, 0x41};
294 uint16_t words[256];
295 makeFixtureData(words, 256);
296 IoEventTrace trace;
297 ScriptedAtaIo command(true, &trace, statuses, 2, words, 256);
298 ScriptedAtaIo control(false, &trace);
299 AtaPioPollBudget budget = makeBudget(2);
300 AtaStatus finalStatus = {};
301
302 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
303 const bool passed =
304 check(!result && finalStatus.__reg_contents == 0x41 && budget.polls == 2 &&
305 command.dataWrites() == 256 && command.writesMatch() && trace.valid(2) &&
306 trace.finalEventWasStatus(),
307 Test, "an ERR status after the final data word was reported as success");
308 if (passed) {
309 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-late-error");
310 }
311 return passed;
312}
313
314bool rejectsDeviceFault() {
315 constexpr const char* Test = "ata-pio-device-fault";
316 const uint8_t statuses[] = {0x08, 0x60};
317 uint16_t words[256];
318 makeFixtureData(words, 256);
319 IoEventTrace trace;
320 ScriptedAtaIo command(true, &trace, statuses, 2, words, 256);
321 ScriptedAtaIo control(false, &trace);
322 AtaPioPollBudget budget = makeBudget(2);
323 AtaStatus finalStatus = {};
324
325 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
326 const bool passed =
327 check(!result && finalStatus.__reg_contents == 0x60 && budget.polls == 2 &&
328 command.dataWrites() == 256 && command.writesMatch() && trace.valid(2) &&
329 trace.finalEventWasStatus(),
330 Test, "a device fault after the final data word was reported as success");
331 if (passed) {
332 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-device-fault");
333 }
334 return passed;
335}
336
337bool requiresDrqForData() {
338 constexpr const char* Test = "ata-pio-requires-drq";
339 const uint8_t statuses[] = {0x40, 0x08};
340 uint16_t words[256];
341 makeFixtureData(words, 256);
342 IoEventTrace trace;
343 ScriptedAtaIo command(true, &trace, statuses, 2, words, 256);
344 ScriptedAtaIo control(false, &trace);
345 AtaPioPollBudget budget = makeBudget(2);
346 AtaStatus finalStatus = {};
347
348 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
349 const bool passed =
350 check(!result && finalStatus.__reg_contents == 0x40 && budget.polls == 1 &&
351 command.statusReads() == 1 && control.alternateReads() == 4 &&
352 command.dataWrites() == 0 && trace.valid(1) && trace.finalEventWasStatus(),
353 Test, "DRDY without DRQ was treated as progress into a later data phase");
354 if (passed) {
355 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-requires-drq");
356 }
357 return passed;
358}
359
360bool rejectsUnexpectedExtraDataPhase() {
361 constexpr const char* Test = "ata-pio-extra-data-phase";
362 const uint8_t statuses[] = {0x08, 0x08, 0x40};
363 uint16_t words[256];
364 makeFixtureData(words, 256);
365 IoEventTrace trace;
366 ScriptedAtaIo command(true, &trace, statuses, 3, words, 256);
367 ScriptedAtaIo control(false, &trace);
368 AtaPioPollBudget budget = makeBudget(3);
369 AtaStatus finalStatus = {};
370
371 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
372 const bool passed =
373 check(!result && finalStatus.__reg_contents == 0x08 && budget.polls == 2 &&
374 command.statusReads() == 2 && control.alternateReads() == 8 &&
375 command.dataWrites() == 256 && command.writesMatch() && trace.valid(2) &&
376 trace.finalEventWasStatus(),
377 Test, "an extra data phase was treated as progress into a later completion state");
378 if (passed) {
379 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-extra-data-phase");
380 }
381 return passed;
382}
383
384bool requiresDrdyForCompletion() {
385 constexpr const char* Test = "ata-pio-terminal-requires-drdy";
386 const uint8_t statuses[] = {0x08, 0x10};
387 uint16_t words[256];
388 makeFixtureData(words, 256);
389 IoEventTrace trace;
390 ScriptedAtaIo command(true, &trace, statuses, 2, words, 256);
391 ScriptedAtaIo control(false, &trace);
392 AtaPioPollBudget budget = makeBudget(2);
393 AtaStatus finalStatus = {};
394
395 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
396 const bool passed =
397 check(!result && finalStatus.__reg_contents == 0x10 && budget.polls == 2 &&
398 command.statusReads() == 2 && control.alternateReads() == 8 &&
399 command.dataWrites() == 256 && command.writesMatch() && trace.valid(2) &&
400 trace.finalEventWasStatus(),
401 Test, "a nonzero terminal status without DRDY was reported as completion");
402 if (passed) {
403 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-terminal-requires-drdy");
404 }
405 return passed;
406}
407
408bool rejectsSecondSectorErrorBeforeData() {
409 constexpr const char* Test = "ata-pio-second-sector-error";
410 const uint8_t statuses[] = {0x08, 0x41};
411 uint16_t words[512];
412 makeFixtureData(words, 512);
413 IoEventTrace trace;
414 ScriptedAtaIo command(true, &trace, statuses, 2, words, 512);
415 ScriptedAtaIo control(false, &trace);
416 AtaPioPollBudget budget = makeBudget(2);
417 AtaStatus finalStatus = {};
418
419 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 2, budget, finalStatus);
420 const bool passed =
421 check(!result && finalStatus.__reg_contents == 0x41 && budget.polls == 2 &&
422 command.statusReads() == 2 && control.alternateReads() == 8 &&
423 command.dataWrites() == 256 && command.writesMatch() && trace.valid(2) &&
424 trace.finalEventWasStatus(),
425 Test, "a second-sector error allowed another sector of data to be written");
426 if (passed) {
427 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-second-sector-error");
428 }
429 return passed;
430}
431
432bool rejectsAlreadyExpiredBudget() {
433 constexpr const char* Test = "ata-pio-expired-timeout";
434 const uint8_t statuses[] = {0x08};
435 uint16_t words[256];
436 makeFixtureData(words, 256);
437 IoEventTrace trace;
438 ScriptedAtaIo command(true, &trace, statuses, 1, words, 256);
439 ScriptedAtaIo control(false, &trace);
440 AtaPioPollBudget budget = {Time::getTicks(), 0, 0, 3};
441 AtaStatus finalStatus = {};
442
443 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
444 const bool passed =
445 check(!result && finalStatus.__reg_contents == 0 && budget.polls == 0 &&
446 command.statusReads() == 0 && control.alternateReads() == 0 &&
447 command.dataWrites() == 0 && trace.valid(0) && !trace.finalEventWasStatus(),
448 Test, "an expired command budget touched status, control, or data registers");
449 if (passed) {
450 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-expired-timeout");
451 }
452 return passed;
453}
454
455bool rejectsZeroStatus() {
456 constexpr const char* Test = "ata-pio-zero-status";
457 const uint8_t statuses[] = {0x00};
458 uint16_t words[256];
459 makeFixtureData(words, 256);
460 IoEventTrace trace;
461 ScriptedAtaIo command(true, &trace, statuses, 1, words, 256);
462 ScriptedAtaIo control(false, &trace);
463 AtaPioPollBudget budget = makeBudget(3);
464 AtaStatus finalStatus = {};
465
466 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
467 const bool passed = check(!result && finalStatus.__reg_contents == 0 && budget.polls == 1 &&
468 command.statusReads() == 1 && command.dataWrites() == 0 &&
469 trace.valid(1) && trace.finalEventWasStatus(),
470 Test, "an absent-device status was retried or reported as success");
471 if (passed) {
472 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-zero-status");
473 }
474 return passed;
475}
476
477bool stopsAtExactPollBoundary() {
478 constexpr const char* Test = "ata-pio-poll-boundary";
479 const uint8_t statuses[] = {0x80};
480 uint16_t words[256];
481 makeFixtureData(words, 256);
482 IoEventTrace trace;
483 ScriptedAtaIo command(true, &trace, statuses, 1, words, 256);
484 ScriptedAtaIo control(false, &trace);
485 AtaPioPollBudget budget = makeBudget(3);
486 AtaStatus finalStatus = {};
487
488 const bool result = ataPioWrite512ByteSectors(&command, &control, words, 1, budget, finalStatus);
489 const bool passed =
490 check(!result && finalStatus.__reg_contents == 0x80 && budget.polls == 3 &&
491 command.statusReads() == 3 && control.alternateReads() == 4 &&
492 command.dataWrites() == 0 && trace.valid(1) && trace.finalEventWasStatus(),
493 Test, "sticky BSY sampled beyond or escaped the exact command poll budget");
494 if (passed) {
495 NOTICE("HOSTED-WAIT-TEST: PASS ata-pio-poll-boundary");
496 }
497 return passed;
498}
499} // namespace
500
501bool runHostedAtaPioRegressions() {
502 bool passed = true;
503 passed &= successfulTwoSectorTransfer();
504 passed &= busyProgressesWithinOneBudget();
505 passed &= ignoresStaleStatusWhileBusy();
506 passed &= rejectsLateError();
507 passed &= rejectsDeviceFault();
508 passed &= requiresDrqForData();
509 passed &= rejectsUnexpectedExtraDataPhase();
510 passed &= requiresDrdyForCompletion();
511 passed &= rejectsSecondSectorErrorBeforeData();
512 passed &= rejectsAlreadyExpiredBudget();
513 passed &= rejectsZeroStatus();
514 passed &= stopsAtExactPollBoundary();
515 return passed;
516}
Abstrace base class for hardware I/O capabilities.
Definition IoBase.h:32
virtual uint8_t read8(size_t offset=0)=0
virtual void write32(uint32_t value, size_t offset=0)=0
virtual uint32_t read32(size_t offset=0)=0
virtual void write8(uint8_t value, size_t offset=0)=0
virtual void write16(uint16_t value, size_t offset=0)=0
virtual uint64_t read64(size_t offset=0)=0
virtual uint16_t read16(size_t offset=0)=0
virtual size_t size() const =0
virtual void write64(uint64_t value, size_t offset=0)=0
uint8_t __reg_contents
"Hidden" integer which contains the actual register contents
Definition ata-common.h:46