Add hardware test suite, fix up IDE driver again

This commit is contained in:
spicyjpeg 2024-06-01 01:49:37 +02:00
parent 82e95ab8d5
commit db5eaabe57
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
17 changed files with 856 additions and 188 deletions

View File

@ -127,6 +127,7 @@ addExecutable(
src/main/app/modals.cpp
src/main/app/romactions.cpp
src/main/app/romworkers.cpp
src/main/app/tests.cpp
src/vendor/ff.c
src/vendor/ffunicode.c
src/vendor/miniz.c

View File

@ -111,6 +111,20 @@
}
},
"AudioTestScreen": {
"title": "{RIGHT_ARROW} Audio output test",
"prompt": "Note that the speaker amplifier and analog audio passthrough are disabled by default and will be disabled again once this screen is closed.",
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
"playLeft": "Play sound on left channel",
"playRight": "Play sound on right channel",
"playBoth": "Play sound on both channels",
"enableAmp": "Turn on built-in speaker amplifier",
"disableAmp": "Turn off built-in speaker amplifier",
"enableCDDA": "Unmute CD-DA/MP3 analog audio passthrough",
"disableCDDA": "Mute CD-DA/MP3 analog audio passthrough"
},
"AutobootScreen": {
"title": "Note",
"body": "A valid boot executable has been found and will be launched shortly. You may disable automatic booting by turning off DIP switch 1 or creating a file named noboot.txt in the root of the filesystem.\n\nFile: %s",
@ -262,6 +276,16 @@
"description": "All checksums are computed using the standard CRC32 algorithm and parameters (polynomial 04C11DB7, initial value FFFFFFFF, input and output reflected, output bits negated)."
},
"ColorIntensityScreen": {
"title": "{RIGHT_ARROW} Monitor color intensity test",
"prompt": "{RIGHT_ARROW} Press {START_BUTTON} to go back.",
"white": "White",
"red": "Red",
"green": "Green",
"blue": "Blue"
},
"ConfirmScreen": {
"title": "Confirm operation",
"no": "No, go back",
@ -288,6 +312,11 @@
"noFilesError": "No files or directories have been found in the root of the selected drive's filesystem."
},
"GeometryScreen": {
"title": "{RIGHT_ARROW} Monitor geometry test",
"prompt": "{RIGHT_ARROW} Press {START_BUTTON} to go back."
},
"HexdumpScreen": {
"title": "{RIGHT_ARROW} Cartridge dump",
"prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back."
@ -328,6 +357,47 @@
}
},
"JAMMATestScreen": {
"title": "{RIGHT_ARROW} JAMMA input test",
"prompt": "{RIGHT_ARROW} Press and hold {START_BUTTON} to go back.",
"noInputs": "No button is currently held down. If a button does not appear here when pressed, make sure the JAMMA harness is wired up correctly and the button is not damaged.\n",
"inputs": "The following buttons are currently held down:\n",
"inputsNote": "\nNote that in DDR cabinets each player's up and right inputs may appear to be stuck due to the presence of the stage I/O boards, which use the joystick inputs as a communication bus.\n",
"p1": {
"left": " Player 1 joystick left\t\tJAMMA pin 20\n",
"right": " Player 1 joystick right\t\tJAMMA pin 21\n",
"up": " Player 1 joystick up\t\tJAMMA pin 18\n",
"down": " Player 1 joystick down\t\tJAMMA pin 19\n",
"button1": " Player 1 button 1\t\tJAMMA pin 22\n",
"button2": " Player 1 button 2\t\tJAMMA pin 23\n",
"button3": " Player 1 button 3\t\tJAMMA pin 24\n",
"button4": " Player 1 button 4\t\tJAMMA pin 25\n",
"button5": " Player 1 button 5\t\tJAMMA pin 26\n",
"button6": " Player 1 button 6\n",
"start": " Player 1 start button ({START_BUTTON})\tJAMMA pin 17\n"
},
"p2": {
"left": " Player 2 joystick left\t\tJAMMA pin X\n",
"right": " Player 2 joystick right\t\tJAMMA pin Y\n",
"up": " Player 2 joystick up\t\tJAMMA pin V\n",
"down": " Player 2 joystick down\t\tJAMMA pin W\n",
"button1": " Player 2 button 1\t\tJAMMA pin Z\n",
"button2": " Player 2 button 2\t\tJAMMA pin a\n",
"button3": " Player 2 button 3\t\tJAMMA pin b\n",
"button4": " Player 2 button 4\t\tJAMMA pin c\n",
"button5": " Player 2 button 5\t\tJAMMA pin d\n",
"button6": " Player 2 button 6\n",
"start": " Player 2 start button ({START_BUTTON})\tJAMMA pin U\n"
},
"coin1": " Coin switch 1\t\t\tJAMMA pin 16\n",
"coin2": " Coin switch 2\t\t\tJAMMA pin T\n",
"test": " Test button\t\t\tJAMMA pin 15\n",
"service": " Service button\t\t\tJAMMA pin R\n"
},
"KeyEntryScreen": {
"title": "Enter unlocking key",
"body": "Enter the 8-byte key this cartridge was last locked with.\n\nUse {LEFT_BUTTON}{RIGHT_BUTTON} to move the cursor, hold {START_BUTTON} and use {LEFT_BUTTON}{RIGHT_BUTTON} to edit the highlighted digit.",
@ -360,6 +430,10 @@
"name": "Set RTC date and time",
"prompt": "Adjust the current date and time. Note that the time will not persist after a power cycle if the RTC's internal battery is empty."
},
"testMenu": {
"name": "Hardware test suite",
"prompt": "Display monitor test patterns, check if all inputs are functional or test the system and cabinet's hardware."
},
"setResolution": {
"name": "Change screen resolution",
"prompt": "Switch to a different screen resolution and aspect ratio. Some monitors and upscalers may not support all resolutions."
@ -549,6 +623,28 @@
"ok": "Confirm"
},
"TestMenuScreen": {
"title": "{RIGHT_ARROW} Hardware test suite",
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
"jammaTest": {
"name": "Test JAMMA inputs",
"prompt": "Show the state of all buttons and inputs wired to the system's JAMMA connector."
},
"audioTest": {
"name": "Test audio output and speaker amplifier",
"prompt": "Play a sound on either audio output channel and test the system's onboard speaker amplification circuitry."
},
"colorIntensity": {
"name": "Check monitor color intensity levels",
"prompt": "Display a series of undithered and dithered color bars to help with monitor color level adjustment and calibration."
},
"geometry": {
"name": "Check monitor geometry and overscan",
"prompt": "Display a grid to help with monitor geometry adjustment. The grid's aspect ratio can be changed by switching to a different screen resolution from the main menu."
}
},
"UnlockKeyScreen": {
"title": "{RIGHT_ARROW} Select unlocking key",
"prompt": "If the cartridge has been converted before, select the game it was last converted to. If it is currently blank, select 00-00-00-00-00-00-00-00.",

View File

@ -98,7 +98,7 @@ bool ISOVolumeDesc::validateMagic(void) const {
bool ISO9660File::_loadSector(uint32_t lba) {
if (lba == _bufferedLBA)
return true;
if (_device->read(_sectorBuffer, lba, 1))
if (_device->readData(_sectorBuffer, lba, 1))
return false;
_bufferedLBA = lba;
@ -131,7 +131,7 @@ size_t ISO9660File::read(void *output, size_t length) {
auto spanLength = numSectors * ide::ATAPI_SECTOR_SIZE;
if (numSectors > 0) {
if (_device->read(currentPtr, lba, numSectors))
if (_device->readData(currentPtr, lba, numSectors))
return false;
offset += spanLength;
@ -204,7 +204,7 @@ bool ISO9660Provider::_readData(
) {
if (!output.allocate(numSectors * ide::ATAPI_SECTOR_SIZE))
return false;
if (_device->read(output.ptr, lba, numSectors))
if (_device->readData(output.ptr, lba, numSectors))
return false;
return true;
@ -272,7 +272,7 @@ bool ISO9660Provider::init(int drive) {
for (
uint32_t lba = _VOLUME_DESC_START_LBA; lba < _VOLUME_DESC_END_LBA; lba++
) {
if (_device->read(&pvd, lba, 1))
if (_device->readData(&pvd, lba, 1))
return false;
if (!pvd.validateMagic()) {
LOG("invalid ISO descriptor, lba=0x%x", lba);

View File

@ -298,7 +298,7 @@ extern "C" DRESULT disk_read(
if (!(dev.flags & ide::DEVICE_READY))
return RES_NOTRDY;
if (dev.read(data, lba, count))
if (dev.readData(data, lba, count))
return RES_ERROR;
return RES_OK;
@ -313,7 +313,7 @@ extern "C" DRESULT disk_write(
return RES_NOTRDY;
if (dev.flags & ide::DEVICE_READ_ONLY)
return RES_WRPRT;
if (dev.write(data, lba, count))
if (dev.writeData(data, lba, count))
return RES_ERROR;
return RES_OK;

View File

@ -22,10 +22,12 @@
namespace ide {
static constexpr int _WAIT_TIMEOUT = 30000000;
static constexpr int _DETECT_TIMEOUT = 500000;
static constexpr int _DMA_TIMEOUT = 10000;
static constexpr int _SRST_DELAY = 5000;
static constexpr int _COMMAND_TIMEOUT = 30000000;
static constexpr int _REQUEST_SENSE_TIMEOUT = 500000;
static constexpr int _DETECT_DRIVE_TIMEOUT = 500000;
static constexpr int _DMA_TIMEOUT = 10000;
static constexpr int _SRST_DELAY = 5000;
static const char *const _SENSE_KEY_NAMES[]{
"NO_SENSE",
@ -52,6 +54,8 @@ const char *const DEVICE_ERROR_NAMES[]{
"NO_DRIVE",
"STATUS_TIMEOUT",
"DRIVE_ERROR",
"DISC_ERROR",
"DISC_CHANGED",
"INCOMPLETE_DATA",
"CHECKSUM_MISMATCH"
};
@ -120,42 +124,19 @@ int IdentifyBlock::getHighestPIOMode(void) const {
Device devices[2]{ (DEVICE_PRIMARY), (DEVICE_SECONDARY) };
void Device::_setLBA(uint64_t lba, size_t count) {
if (flags & DEVICE_HAS_LBA48) {
//assert(lba < (1ULL << 48));
//assert(count <= (1 << 16));
_select(CS0_DEVICE_SEL_LBA);
_write(CS0_COUNT, (count >> 8) & 0xff);
_write(CS0_SECTOR, (lba >> 24) & 0xff);
_write(CS0_CYLINDER_L, (lba >> 32) & 0xff);
_write(CS0_CYLINDER_H, (lba >> 40) & 0xff);
} else {
//assert(lba < (1ULL << 28));
//assert(count <= (1 << 8));
_select(CS0_DEVICE_SEL_LBA | ((lba >> 24) & 15));
}
_write(CS0_COUNT, (count >> 0) & 0xff);
_write(CS0_SECTOR, (lba >> 0) & 0xff);
_write(CS0_CYLINDER_L, (lba >> 8) & 0xff);
_write(CS0_CYLINDER_H, (lba >> 16) & 0xff);
}
DeviceError Device::_waitForStatus(
uint8_t mask, uint8_t value, int timeout, bool ignoreErrors
) {
if (!timeout)
timeout = _WAIT_TIMEOUT;
timeout = _COMMAND_TIMEOUT;
for (; timeout > 0; timeout -= 10) {
uint8_t status = _read(CS0_STATUS);
if (!ignoreErrors && (status & CS0_STATUS_ERR)) {
LOG(
"IDE error, stat=0x%02x, err=0x%02x", _read(CS0_STATUS),
"drive %d error, stat=0x%02x, err=0x%02x",
(flags / DEVICE_SECONDARY) & 1, _read(CS0_STATUS),
_read(CS0_ERROR)
);
@ -173,26 +154,55 @@ DeviceError Device::_waitForStatus(
}
LOG(
"IDE timeout, stat=0x%02x, err=0x%02x", _read(CS0_STATUS),
_read(CS0_ERROR)
"drive %d timeout, stat=0x%02x, err=0x%02x",
(flags / DEVICE_SECONDARY) & 1, _read(CS0_STATUS), _read(CS0_ERROR)
);
_write(CS0_COMMAND, ATA_DEVICE_RESET);
return STATUS_TIMEOUT;
}
DeviceError Device::_command(
uint8_t cmd, uint8_t status, int timeout, bool ignoreErrors
DeviceError Device::_select(
uint8_t devSelFlags, int timeout, bool ignoreErrors
) {
auto error = _waitForStatus(
CS0_STATUS_BSY | status, status, timeout, ignoreErrors
);
if (flags & DEVICE_SECONDARY)
_write(CS0_DEVICE_SEL, devSelFlags | CS0_DEVICE_SEL_SECONDARY);
else
_write(CS0_DEVICE_SEL, devSelFlags | CS0_DEVICE_SEL_PRIMARY);
if (error)
return error;
return _waitForStatus(CS0_STATUS_BSY, 0, timeout, ignoreErrors);
}
_write(CS0_COMMAND, cmd);
return _waitForStatus(CS0_STATUS_BSY, 0, timeout);
DeviceError Device::_setLBA(uint64_t lba, size_t count, int timeout) {
if (flags & DEVICE_HAS_LBA48) {
//assert(lba < (1ULL << 48));
//assert(count <= (1 << 16));
auto error = _select(CS0_DEVICE_SEL_LBA, timeout);
if (error)
return error;
_write(CS0_COUNT, (count >> 8) & 0xff);
_write(CS0_SECTOR, (lba >> 24) & 0xff);
_write(CS0_CYLINDER_L, (lba >> 32) & 0xff);
_write(CS0_CYLINDER_H, (lba >> 40) & 0xff);
} else {
//assert(lba < (1ULL << 28));
//assert(count <= (1 << 8));
auto error = _select(CS0_DEVICE_SEL_LBA | ((lba >> 24) & 15), timeout);
if (error)
return error;
}
_write(CS0_COUNT, (count >> 0) & 0xff);
_write(CS0_SECTOR, (lba >> 0) & 0xff);
_write(CS0_CYLINDER_L, (lba >> 8) & 0xff);
_write(CS0_CYLINDER_H, (lba >> 16) & 0xff);
return NO_ERROR;
}
DeviceError Device::_detectDrive(void) {
@ -202,7 +212,11 @@ DeviceError Device::_detectDrive(void) {
_write(CS1_DEVICE_CTRL, CS1_DEVICE_CTRL_IEN);
delayMicroseconds(_SRST_DELAY);
_select();
if (_select(0, _DETECT_DRIVE_TIMEOUT, true)) {
LOG("drive %d select timeout", (flags / DEVICE_SECONDARY) & 1);
return NO_DRIVE;
}
#ifndef ENABLE_FULL_IDE_DRIVER
io::clearWatchdog();
#endif
@ -211,7 +225,7 @@ DeviceError Device::_detectDrive(void) {
// the written value. This should not fail even if the drive is busy.
uint8_t pattern = 0x55;
for (int timeout = _DETECT_TIMEOUT; timeout > 0; timeout -= 10) {
for (int timeout = _DETECT_DRIVE_TIMEOUT; timeout > 0; timeout -= 100) {
_write(CS0_COUNT, pattern);
// Note that ATA drives will also assert DRDY when ready, but ATAPI
@ -223,7 +237,7 @@ DeviceError Device::_detectDrive(void) {
if (!(pattern & 1))
pattern |= 1 << 7;
delayMicroseconds(10);
delayMicroseconds(100);
#ifndef ENABLE_FULL_IDE_DRIVER
io::clearWatchdog();
#endif
@ -233,10 +247,14 @@ DeviceError Device::_detectDrive(void) {
return NO_DRIVE;
}
DeviceError Device::_readPIO(void *data, size_t length, int timeout) {
DeviceError Device::_readPIO(
void *data, size_t length, int timeout, bool ignoreErrors
) {
util::assertAligned<uint16_t>(data);
auto error = _waitForStatus(CS0_STATUS_DRQ, CS0_STATUS_DRQ, timeout);
auto error = _waitForStatus(
CS0_STATUS_DRQ | CS0_STATUS_BSY, CS0_STATUS_DRQ, timeout
);
if (error)
return error;
@ -249,10 +267,14 @@ DeviceError Device::_readPIO(void *data, size_t length, int timeout) {
return NO_ERROR;
}
DeviceError Device::_writePIO(const void *data, size_t length, int timeout) {
DeviceError Device::_writePIO(
const void *data, size_t length, int timeout, bool ignoreErrors
) {
util::assertAligned<uint16_t>(data);
auto error = _waitForStatus(CS0_STATUS_DRQ, CS0_STATUS_DRQ, timeout);
auto error = _waitForStatus(
CS0_STATUS_DRQ | CS0_STATUS_BSY, CS0_STATUS_DRQ, timeout
);
if (error)
return error;
@ -265,12 +287,16 @@ DeviceError Device::_writePIO(const void *data, size_t length, int timeout) {
return NO_ERROR;
}
DeviceError Device::_readDMA(void *data, size_t length, int timeout) {
DeviceError Device::_readDMA(
void *data, size_t length, int timeout, bool ignoreErrors
) {
length /= 4;
util::assertAligned<uint32_t>(data);
auto error = _waitForStatus(CS0_STATUS_DRQ, CS0_STATUS_DRQ, timeout);
auto error = _waitForStatus(
CS0_STATUS_DRQ | CS0_STATUS_BSY, CS0_STATUS_DRQ, timeout
);
if (error)
return error;
@ -291,12 +317,16 @@ DeviceError Device::_readDMA(void *data, size_t length, int timeout) {
return NO_ERROR;
}
DeviceError Device::_writeDMA(const void *data, size_t length, int timeout) {
DeviceError Device::_writeDMA(
const void *data, size_t length, int timeout, bool ignoreErrors
) {
length /= 4;
util::assertAligned<uint32_t>(data);
auto error = _waitForStatus(CS0_STATUS_DRQ, CS0_STATUS_DRQ, timeout);
auto error = _waitForStatus(
CS0_STATUS_DRQ | CS0_STATUS_BSY, CS0_STATUS_DRQ, timeout
);
if (error)
return error;
@ -334,12 +364,13 @@ DeviceError Device::_ideReadWrite(
while (count) {
size_t chunkLength = util::min(count, maxLength);
_setLBA(lba, chunkLength);
auto error = _command(cmd, CS0_STATUS_DRDY);
auto error = _setLBA(lba, chunkLength);
if (error)
return error;
_write(CS0_COMMAND, cmd);
// Data must be transferred one sector at a time as the drive may
// deassert DRQ between sectors.
for (size_t i = chunkLength; i; i--) {
@ -359,7 +390,7 @@ DeviceError Device::_ideReadWrite(
}
error = _waitForStatus(
CS0_STATUS_BSY | CS0_STATUS_DRDY, CS0_STATUS_DRDY
CS0_STATUS_DRDY | CS0_STATUS_BSY, CS0_STATUS_DRDY
);
if (error)
@ -376,7 +407,7 @@ DeviceError Device::_atapiRead(uintptr_t ptr, uint32_t lba, size_t count) {
Packet packet;
packet.setRead(lba, count);
auto error = atapiPacket(packet);
auto error = atapiPacket(packet, ATAPI_SECTOR_SIZE);
if (error)
return error;
@ -384,10 +415,8 @@ DeviceError Device::_atapiRead(uintptr_t ptr, uint32_t lba, size_t count) {
// Data must be transferred one sector at a time as the drive may deassert
// DRQ between sectors.
for (; count; count--) {
error = _readPIO(reinterpret_cast<void *>(ptr), ATAPI_SECTOR_SIZE);
if (error)
return error;
if (_readPIO(reinterpret_cast<void *>(ptr), ATAPI_SECTOR_SIZE))
return atapiPoll();
ptr += ATAPI_SECTOR_SIZE;
}
@ -411,16 +440,18 @@ DeviceError Device::enumerate(void) {
// to prevent blocking for too long.
IdentifyBlock block;
//_select();
if ((_read(CS0_CYLINDER_L) == 0x14) && (_read(CS0_CYLINDER_H) == 0xeb)) {
flags |= DEVICE_ATAPI;
if (_command(ATA_IDENTIFY_PACKET, 0, _DETECT_TIMEOUT))
return NO_DRIVE;
if (_readPIO(&block, sizeof(IdentifyBlock), _DETECT_TIMEOUT))
return NO_DRIVE;
_write(CS0_COMMAND, ATA_IDENTIFY_PACKET);
if (_readPIO(&block, sizeof(IdentifyBlock), _DETECT_DRIVE_TIMEOUT))
return NO_DRIVE;
if (!block.validateChecksum())
return CHECKSUM_MISMATCH;
if (
(block.deviceFlags & IDENTIFY_DEV_ATAPI_TYPE_BITMASK)
== IDENTIFY_DEV_ATAPI_TYPE_CDROM
@ -432,13 +463,13 @@ DeviceError Device::enumerate(void) {
)
flags |= DEVICE_HAS_PACKET16;
} else {
if (_command(ATA_IDENTIFY, CS0_STATUS_DRDY, _DETECT_TIMEOUT))
return NO_DRIVE;
if (_readPIO(&block, sizeof(IdentifyBlock), _DETECT_TIMEOUT))
return NO_DRIVE;
_write(CS0_COMMAND, ATA_IDENTIFY);
if (_readPIO(&block, sizeof(IdentifyBlock), _DETECT_DRIVE_TIMEOUT))
return NO_DRIVE;
if (!block.validateChecksum())
return CHECKSUM_MISMATCH;
if (block.commandSetFlags[1] & (1 << 10)) {
flags |= DEVICE_HAS_LBA48;
capacity = block.getSectorCountExt();
@ -462,8 +493,9 @@ DeviceError Device::enumerate(void) {
_write(CS0_FEATURES, FEATURE_TRANSFER_MODE);
_write(CS0_COUNT, (1 << 3) | mode);
_write(CS0_COMMAND, ATA_SET_FEATURES);
error = _command(ATA_SET_FEATURES, 0);
error = _waitForStatus(CS0_STATUS_BSY, 0);
if (error)
return error;
@ -479,22 +511,29 @@ DeviceError Device::atapiPacket(const Packet &packet, size_t transferLength) {
if (!(flags & DEVICE_ATAPI))
return UNSUPPORTED_OP;
_select();
auto error = _select(0);
if (error)
return atapiPoll();
_write(CS0_CYLINDER_L, (transferLength >> 0) & 0xff);
_write(CS0_CYLINDER_H, (transferLength >> 8) & 0xff);
_write(CS0_COMMAND, ATA_PACKET);
auto error = _command(ATA_PACKET, 0);
error = _writePIO(&packet, (flags & DEVICE_HAS_PACKET16) ? 16 : 12);
if (!error)
error = _writePIO(&packet, (flags & DEVICE_HAS_PACKET16) ? 16 : 12);
if (!error)
return _waitForStatus(CS0_STATUS_BSY, 0);
if (error)
return atapiPoll();
return atapiPoll();
return _waitForStatus(CS0_STATUS_BSY, 0);
}
DeviceError Device::atapiPoll(void) {
if (!(flags & DEVICE_READY))
return NO_DRIVE;
if (!(flags & DEVICE_ATAPI))
return UNSUPPORTED_OP;
Packet packet;
SenseData data;
@ -502,14 +541,23 @@ DeviceError Device::atapiPoll(void) {
// If an error occurs, the error flag in the status register will be set but
// the drive will still accept a request sense command.
auto error = _command(ATA_PACKET, 0, 0, true);
auto error = _select(0, _REQUEST_SENSE_TIMEOUT, true);
if (error)
return error;
_write(CS0_CYLINDER_L, (sizeof(data) >> 0) & 0xff);
_write(CS0_CYLINDER_H, (sizeof(data) >> 8) & 0xff);
_write(CS0_COMMAND, ATA_PACKET);
error = _writePIO(
&packet, (flags & DEVICE_HAS_PACKET16) ? 16 : 12, _REQUEST_SENSE_TIMEOUT
);
//if (!error)
//error = _waitForStatus(CS0_STATUS_BSY, 0, _REQUEST_SENSE_TIMEOUT);
if (!error)
error = _writePIO(&packet, (flags & DEVICE_HAS_PACKET16) ? 16 : 12);
if (!error)
error = _waitForStatus(CS0_STATUS_BSY, 0);
if (!error)
error = _readPIO(&data, sizeof(data));
error = _readPIO(&data, sizeof(data), _REQUEST_SENSE_TIMEOUT);
int senseKey;
@ -526,10 +574,11 @@ DeviceError Device::atapiPoll(void) {
);
}
LOG("%s (%d)", _SENSE_KEY_NAMES[senseKey], senseKey);
LOG("sense key: %s (%d)", _SENSE_KEY_NAMES[senseKey], senseKey);
switch (senseKey) {
case SENSE_KEY_NO_SENSE:
case SENSE_KEY_RECOVERED_ERROR:
return NO_ERROR;
case SENSE_KEY_NOT_READY:
@ -545,31 +594,23 @@ DeviceError Device::atapiPoll(void) {
}
}
DeviceError Device::read(void *data, uint64_t lba, size_t count) {
DeviceError Device::readData(void *data, uint64_t lba, size_t count) {
util::assertAligned<uint32_t>(data);
if (!(flags & DEVICE_READY))
return NO_DRIVE;
if (flags & DEVICE_ATAPI) {
auto error = _atapiRead(
if (flags & DEVICE_ATAPI)
return _atapiRead(
reinterpret_cast<uintptr_t>(data), static_cast<uint32_t>(lba), count
);
#ifdef ENABLE_FULL_IDE_DRIVER
if (error)
error = atapiPoll();
#endif
return error;
} else {
else
return _ideReadWrite(
reinterpret_cast<uintptr_t>(data), lba, count, false
);
}
}
DeviceError Device::write(const void *data, uint64_t lba, size_t count) {
DeviceError Device::writeData(const void *data, uint64_t lba, size_t count) {
util::assertAligned<uint32_t>(data);
if (!(flags & DEVICE_READY))
@ -590,25 +631,17 @@ DeviceError Device::goIdle(bool standby) {
packet.setStartStopUnit(START_STOP_MODE_STOP_DISC);
return atapiPacket(packet);
} else {
_select();
auto error = _select(CS0_DEVICE_SEL_LBA);
auto error = _command(
standby ? ATA_STANDBY_IMMEDIATE : ATA_IDLE_IMMEDIATE,
CS0_STATUS_DRDY
);
if (error)
return error;
#if 0
if (error) {
// If the immediate command failed, fall back to setting the
// inactivity timeout to the lowest allowed value (5 seconds).
// FIXME: the original timeout would have to be restored once the
// drive is accessed again
_write(CS0_COUNT, 1);
error = _command(standby ? ATA_STANDBY : ATA_IDLE, CS0_STATUS_DRDY);
}
#endif
if (standby)
_write(CS0_COMMAND, ATA_STANDBY_IMMEDIATE);
else
_write(CS0_COMMAND, ATA_IDLE_IMMEDIATE);
return error;
return _waitForStatus(CS0_STATUS_BSY, 0);
}
}
@ -619,12 +652,17 @@ DeviceError Device::flushCache(void) {
return NO_ERROR;
//return UNSUPPORTED_OP;
_select();
auto error = _select(CS0_DEVICE_SEL_LBA);
return _command(
(flags & DEVICE_HAS_LBA48) ? ATA_FLUSH_CACHE_EXT : ATA_FLUSH_CACHE,
CS0_STATUS_DRDY
);
if (error)
return error;
if (flags & DEVICE_HAS_LBA48)
_write(CS0_COMMAND, ATA_FLUSH_CACHE_EXT);
else
_write(CS0_COMMAND, ATA_FLUSH_CACHE);
return _waitForStatus(CS0_STATUS_DRDY | CS0_STATUS_BSY, CS0_STATUS_DRDY);
}
}

View File

@ -370,26 +370,29 @@ private:
SYS573_IDE_CS1_BASE[reg] = value;
}
inline void _select(uint8_t regFlags = 0) {
if (flags & DEVICE_SECONDARY)
_write(CS0_DEVICE_SEL, regFlags | CS0_DEVICE_SEL_SECONDARY);
else
_write(CS0_DEVICE_SEL, regFlags | CS0_DEVICE_SEL_PRIMARY);
}
void _setLBA(uint64_t lba, size_t count);
DeviceError _waitForStatus(
uint8_t mask, uint8_t value, int timeout = 0, bool ignoreErrors = false
);
DeviceError _command(
uint8_t cmd, uint8_t status, int timeout = 0, bool ignoreErrors = false
DeviceError _select(
uint8_t devSelFlags, int timeout = 0, bool ignoreErrors = false
);
DeviceError _setLBA(uint64_t lba, size_t count, int timeout = 0);
DeviceError _detectDrive(void);
DeviceError _readPIO(void *data, size_t length, int timeout = 0);
DeviceError _writePIO(const void *data, size_t length, int timeout = 0);
DeviceError _readDMA(void *data, size_t length, int timeout = 0);
DeviceError _writeDMA(const void *data, size_t length, int timeout = 0);
DeviceError _readPIO(
void *data, size_t length, int timeout = 0, bool ignoreErrors = false
);
DeviceError _writePIO(
const void *data, size_t length, int timeout = 0,
bool ignoreErrors = false
);
DeviceError _readDMA(
void *data, size_t length, int timeout = 0, bool ignoreErrors = false
);
DeviceError _writeDMA(
const void *data, size_t length, int timeout = 0,
bool ignoreErrors = false
);
DeviceError _ideReadWrite(
uintptr_t ptr, uint64_t lba, size_t count, bool write
@ -421,13 +424,11 @@ public:
}
DeviceError enumerate(void);
DeviceError atapiPacket(
const Packet &packet, size_t transferLength = ATAPI_SECTOR_SIZE
);
DeviceError atapiPacket(const Packet &packet, size_t transferLength = 0);
DeviceError atapiPoll(void);
DeviceError read(void *data, uint64_t lba, size_t count);
DeviceError write(const void *data, uint64_t lba, size_t count);
DeviceError readData(void *data, uint64_t lba, size_t count);
DeviceError writeData(const void *data, uint64_t lba, size_t count);
DeviceError goIdle(bool standby = false);
DeviceError flushCache(void);
};

View File

@ -165,14 +165,14 @@ bool Sound::initFromVAGHeader(const VAGHeader *header, uint32_t ramOffset) {
return true;
}
Channel Sound::play(uint16_t volume, Channel ch) const {
Channel Sound::play(uint16_t left, uint16_t right, Channel ch) const {
if ((ch < 0) || (ch >= NUM_CHANNELS))
return -1;
if (!offset)
return -1;
SPU_CH_VOL_L(ch) = volume;
SPU_CH_VOL_R(ch) = volume;
SPU_CH_VOL_L(ch) = left;
SPU_CH_VOL_R(ch) = right;
SPU_CH_FREQ (ch) = sampleRate;
SPU_CH_ADDR (ch) = offset;
SPU_CH_ADSR1(ch) = 0x00ff;

View File

@ -24,12 +24,12 @@ static inline void setMasterVolume(uint16_t master, uint16_t reverb = 0) {
SPU_REVERB_VOL_R = reverb;
}
static inline void setChannelVolume(Channel ch, uint16_t volume) {
static inline void setChannelVolume(Channel ch, uint16_t left, uint16_t right) {
if ((ch < 0) || (ch >= NUM_CHANNELS))
return;
SPU_CH_VOL_L(ch) = volume;
SPU_CH_VOL_R(ch) = volume;
SPU_CH_VOL_L(ch) = left;
SPU_CH_VOL_R(ch) = right;
}
void init(void);
@ -54,12 +54,14 @@ public:
inline Sound(void)
: offset(0), length(0) {}
inline Channel play(uint16_t volume = MAX_VOLUME) const {
return play(volume, getFreeChannel());
inline Channel play(
uint16_t left = MAX_VOLUME, uint16_t right = MAX_VOLUME
) const {
return play(left, right, getFreeChannel());
}
bool initFromVAGHeader(const VAGHeader *header, uint32_t ramOffset);
Channel play(uint16_t volume, Channel ch) const;
Channel play(uint16_t left, uint16_t right, Channel ch) const;
};
}

View File

@ -16,7 +16,7 @@ int main(int argc, const char **argv) {
args.parseArgument(*(argv++));
#ifdef ENABLE_LOGGING
util::logger.setupSyslog(launcher.args.baudRate);
util::logger.setupSyslog(args.baudRate);
#endif
if (!args.entryPoint || !args.loadAddress || !args.numFragments)
@ -52,7 +52,7 @@ int main(int argc, const char **argv) {
length -= skipSectors;
}
if (dev.read(reinterpret_cast<void *>(ptr), lba, length))
if (dev.readData(reinterpret_cast<void *>(ptr), lba, length))
return 3;
io::clearWatchdog();

View File

@ -342,6 +342,7 @@ void App::_interruptHandler(void) {
_runWorker(&App::_ideInitWorker, _warningScreen);
_setupInterrupts();
_ctx.sounds[ui::SOUND_STARTUP].play();
for (;;) {
util::Date date;

View File

@ -12,6 +12,7 @@
#include "main/app/misc.hpp"
#include "main/app/modals.hpp"
#include "main/app/romactions.hpp"
#include "main/app/tests.hpp"
#include "main/cart.hpp"
#include "main/cartdata.hpp"
#include "main/cartio.hpp"
@ -55,7 +56,7 @@ private:
public:
file::Provider *ide[util::countOf(ide::devices)];
file::ZIPProvider resource;
#ifndef NDEBUG
#ifdef ENABLE_PCDRV
file::HostProvider host;
#endif
file::VFSProvider vfs;
@ -81,54 +82,84 @@ class App {
friend class ConfirmScreen;
friend class FilePickerScreen;
friend class FileBrowserScreen;
friend class AutobootScreen;
friend class WarningScreen;
friend class AutobootScreen;
friend class ButtonMappingScreen;
friend class MainMenuScreen;
friend class StorageInfoScreen;
friend class StorageActionsScreen;
friend class IDEInfoScreen;
friend class RTCTimeScreen;
friend class ResolutionScreen;
friend class AboutScreen;
friend class CartInfoScreen;
friend class UnlockKeyScreen;
friend class KeyEntryScreen;
friend class CartActionsScreen;
friend class QRCodeScreen;
friend class HexdumpScreen;
friend class ReflashGameScreen;
friend class SystemIDEntryScreen;
friend class StorageInfoScreen;
friend class StorageActionsScreen;
friend class CardSizeScreen;
friend class ChecksumScreen;
friend class TestMenuScreen;
friend class JAMMATestScreen;
friend class AudioTestScreen;
friend class TestPatternScreen;
friend class ColorIntensityScreen;
friend class GeometryScreen;
friend class IDEInfoScreen;
friend class RTCTimeScreen;
friend class ResolutionScreen;
friend class AboutScreen;
private:
WorkerStatusScreen _workerStatusScreen;
MessageScreen _messageScreen;
ConfirmScreen _confirmScreen;
FilePickerScreen _filePickerScreen;
FileBrowserScreen _fileBrowserScreen;
AutobootScreen _autobootScreen;
WarningScreen _warningScreen;
ButtonMappingScreen _buttonMappingScreen;
MainMenuScreen _mainMenuScreen;
// modals.cpp
WorkerStatusScreen _workerStatusScreen;
MessageScreen _messageScreen;
ConfirmScreen _confirmScreen;
FilePickerScreen _filePickerScreen;
FileBrowserScreen _fileBrowserScreen;
// main.cpp
WarningScreen _warningScreen;
AutobootScreen _autobootScreen;
ButtonMappingScreen _buttonMappingScreen;
MainMenuScreen _mainMenuScreen;
// cartunlock.cpp
CartInfoScreen _cartInfoScreen;
UnlockKeyScreen _unlockKeyScreen;
KeyEntryScreen _keyEntryScreen;
// cartactions.cpp
CartActionsScreen _cartActionsScreen;
QRCodeScreen _qrCodeScreen;
HexdumpScreen _hexdumpScreen;
ReflashGameScreen _reflashGameScreen;
SystemIDEntryScreen _systemIDEntryScreen;
// romactions.cpp
StorageInfoScreen _storageInfoScreen;
StorageActionsScreen _storageActionsScreen;
IDEInfoScreen _ideInfoScreen;
RTCTimeScreen _rtcTimeScreen;
ResolutionScreen _resolutionScreen;
AboutScreen _aboutScreen;
CartInfoScreen _cartInfoScreen;
UnlockKeyScreen _unlockKeyScreen;
KeyEntryScreen _keyEntryScreen;
CartActionsScreen _cartActionsScreen;
QRCodeScreen _qrCodeScreen;
HexdumpScreen _hexdumpScreen;
ReflashGameScreen _reflashGameScreen;
SystemIDEntryScreen _systemIDEntryScreen;
CardSizeScreen _cardSizeScreen;
ChecksumScreen _checksumScreen;
// tests.cpp
TestMenuScreen _testMenuScreen;
JAMMATestScreen _jammaTestScreen;
AudioTestScreen _audioTestScreen;
ColorIntensityScreen _colorIntensityScreen;
GeometryScreen _geometryScreen;
// misc.cpp
IDEInfoScreen _ideInfoScreen;
RTCTimeScreen _rtcTimeScreen;
ResolutionScreen _resolutionScreen;
AboutScreen _aboutScreen;
#ifdef ENABLE_LOG_BUFFER
util::LogBuffer _logBuffer;
ui::LogOverlay _logOverlay;
@ -166,6 +197,9 @@ private:
bool playSound = false
);
void _worker(void);
void _interruptHandler(void);
// cartworkers.cpp
bool _cartDetectWorker(void);
bool _cartUnlockWorker(void);
@ -189,9 +223,6 @@ private:
bool _atapiEjectWorker(void);
bool _rebootWorker(void);
void _worker(void);
void _interruptHandler(void);
public:
App(ui::Context &ctx);
~App(void);

View File

@ -154,6 +154,10 @@ static const MenuEntry _MENU_ENTRIES[]{
.name = "MainMenuScreen.setRTCTime.name"_h,
.prompt = "MainMenuScreen.setRTCTime.prompt"_h,
.target = &MainMenuScreen::setRTCTime
}, {
.name = "MainMenuScreen.testMenu.name"_h,
.prompt = "MainMenuScreen.testMenu.prompt"_h,
.target = &MainMenuScreen::testMenu
}, {
.name = "MainMenuScreen.setResolution.name"_h,
.prompt = "MainMenuScreen.setResolution.prompt"_h,
@ -215,6 +219,10 @@ void MainMenuScreen::setRTCTime(ui::Context &ctx) {
ctx.show(APP->_rtcTimeScreen, false, true);
}
void MainMenuScreen::testMenu(ui::Context &ctx) {
ctx.show(APP->_testMenuScreen, false, true);
}
void MainMenuScreen::setResolution(ui::Context &ctx) {
ctx.show(APP->_resolutionScreen, false, true);
}

View File

@ -48,6 +48,7 @@ public:
void ideInfo(ui::Context &ctx);
void runExecutable(ui::Context &ctx);
void setRTCTime(ui::Context &ctx);
void testMenu(ui::Context &ctx);
void setResolution(ui::Context &ctx);
void about(ui::Context &ctx);
void ejectCD(ui::Context &ctx);

View File

@ -277,7 +277,7 @@ void AboutScreen::show(ui::Context &ctx, bool goBack) {
ctx.time, 0, _LOOP_FADE_IN_VOLUME,
ctx.gpuCtx.refreshRate * _LOOP_FADE_IN_TIME
);
_loopChannel = ctx.sounds[ui::SOUND_ABOUT_SCREEN].play(0);
_loopChannel = ctx.sounds[ui::SOUND_ABOUT_SCREEN].play(0, 0);
}
void AboutScreen::hide(ui::Context &ctx, bool goBack) {
@ -290,7 +290,9 @@ void AboutScreen::hide(ui::Context &ctx, bool goBack) {
void AboutScreen::update(ui::Context &ctx) {
TextScreen::update(ctx);
spu::setChannelVolume(_loopChannel, _loopVolume.getValue(ctx.time));
auto volume = _loopVolume.getValue(ctx.time);
spu::setChannelVolume(_loopChannel, volume, volume);
if (ctx.buttons.pressed(ui::BTN_START))
ctx.show(APP->_mainMenuScreen, true, true);

View File

@ -59,7 +59,6 @@ bool App::_ideInitWorker(void) {
}
#endif
_ctx.sounds[ui::SOUND_STARTUP].play();
return true;
}

416
src/main/app/tests.cpp Normal file
View File

@ -0,0 +1,416 @@
#include "common/gpu.hpp"
#include "common/io.hpp"
#include "common/spu.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "main/app/tests.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
#include "tests.hpp"
/* Top-level test menu */
struct TestMenuEntry {
public:
util::Hash name, prompt;
void (TestMenuScreen::*target)(ui::Context &ctx);
};
static const TestMenuEntry _TEST_MENU_ENTRIES[]{
{
.name = "TestMenuScreen.jammaTest.name"_h,
.prompt = "TestMenuScreen.jammaTest.prompt"_h,
.target = &TestMenuScreen::jammaTest
}, {
.name = "TestMenuScreen.audioTest.name"_h,
.prompt = "TestMenuScreen.audioTest.prompt"_h,
.target = &TestMenuScreen::audioTest
}, {
.name = "TestMenuScreen.colorIntensity.name"_h,
.prompt = "TestMenuScreen.colorIntensity.prompt"_h,
.target = &TestMenuScreen::colorIntensity
}, {
.name = "TestMenuScreen.geometry.name"_h,
.prompt = "TestMenuScreen.geometry.prompt"_h,
.target = &TestMenuScreen::geometry
}
};
const char *TestMenuScreen::_getItemName(ui::Context &ctx, int index) const {
return STRH(_TEST_MENU_ENTRIES[index].name);
}
void TestMenuScreen::jammaTest(ui::Context &ctx) {
ctx.show(APP->_jammaTestScreen, false, true);
}
void TestMenuScreen::audioTest(ui::Context &ctx) {
ctx.show(APP->_audioTestScreen, false, true);
}
void TestMenuScreen::colorIntensity(ui::Context &ctx) {
ctx.show(APP->_colorIntensityScreen, false, true);
}
void TestMenuScreen::geometry(ui::Context &ctx) {
ctx.show(APP->_geometryScreen, false, true);
}
void TestMenuScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("TestMenuScreen.title");
_prompt = STRH(_TEST_MENU_ENTRIES[0].prompt);
_itemPrompt = STR("TestMenuScreen.itemPrompt");
_listLength = util::countOf(_TEST_MENU_ENTRIES);
ListScreen::show(ctx, goBack);
}
void TestMenuScreen::update(ui::Context &ctx) {
auto &entry = _TEST_MENU_ENTRIES[_activeItem];
_prompt = STRH(entry.prompt);
ListScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START)) {
if (ctx.buttons.held(ui::BTN_LEFT) || ctx.buttons.held(ui::BTN_RIGHT))
ctx.show(APP->_mainMenuScreen, true, true);
else
(this->*entry.target)(ctx);
}
}
/* Test submenus */
static const util::Hash _JAMMA_INPUT_NAMES[]{
"JAMMATestScreen.p2.left"_h, // io::JAMMA_P2_LEFT
"JAMMATestScreen.p2.right"_h, // io::JAMMA_P2_RIGHT
"JAMMATestScreen.p2.up"_h, // io::JAMMA_P2_UP
"JAMMATestScreen.p2.down"_h, // io::JAMMA_P2_DOWN
"JAMMATestScreen.p2.button1"_h, // io::JAMMA_P2_BUTTON1
"JAMMATestScreen.p2.button2"_h, // io::JAMMA_P2_BUTTON2
"JAMMATestScreen.p2.button3"_h, // io::JAMMA_P2_BUTTON3
"JAMMATestScreen.p2.start"_h, // io::JAMMA_P2_START
"JAMMATestScreen.p1.left"_h, // io::JAMMA_P1_LEFT
"JAMMATestScreen.p1.right"_h, // io::JAMMA_P1_RIGHT
"JAMMATestScreen.p1.up"_h, // io::JAMMA_P1_UP
"JAMMATestScreen.p1.down"_h, // io::JAMMA_P1_DOWN
"JAMMATestScreen.p1.button1"_h, // io::JAMMA_P1_BUTTON1
"JAMMATestScreen.p1.button2"_h, // io::JAMMA_P1_BUTTON2
"JAMMATestScreen.p1.button3"_h, // io::JAMMA_P1_BUTTON3
"JAMMATestScreen.p1.start"_h, // io::JAMMA_P1_START
"JAMMATestScreen.p1.button4"_h, // io::JAMMA_P1_BUTTON4
"JAMMATestScreen.p1.button5"_h, // io::JAMMA_P1_BUTTON5
"JAMMATestScreen.test"_h, // io::JAMMA_TEST
"JAMMATestScreen.p1.button6"_h, // io::JAMMA_P1_BUTTON6
"JAMMATestScreen.p2.button4"_h, // io::JAMMA_P2_BUTTON4
"JAMMATestScreen.p2.button5"_h, // io::JAMMA_P2_BUTTON5
0, // io::JAMMA_RAM_LAYOUT
"JAMMATestScreen.p2.button6"_h, // io::JAMMA_P2_BUTTON6
"JAMMATestScreen.coin1"_h, // io::JAMMA_COIN1
"JAMMATestScreen.coin2"_h, // io::JAMMA_COIN2
0, // io::JAMMA_PCMCIA_CD1
0, // io::JAMMA_PCMCIA_CD2
"JAMMATestScreen.service"_h // io::JAMMA_SERVICE
};
#define _PRINT(...) (ptr += snprintf(ptr, end - ptr __VA_OPT__(,) __VA_ARGS__))
#define _PRINTLN() (*(ptr++) = '\n')
void JAMMATestScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("JAMMATestScreen.title");
_body = _bodyText;
_prompt = STR("JAMMATestScreen.prompt");
_bodyText[0] = 0;
TextScreen::show(ctx, goBack);
}
void JAMMATestScreen::update(ui::Context &ctx) {
char *ptr = _bodyText, *end = &_bodyText[sizeof(_bodyText)];
auto inputs = io::getJAMMAInputs();
if (inputs) {
_PRINT(STR("JAMMATestScreen.inputs"));
for (auto name : _JAMMA_INPUT_NAMES) {
if ((inputs & 1) && name)
_PRINT(STRH(name));
inputs >>= 1;
}
_PRINT(STR("JAMMATestScreen.inputsNote"));
} else {
_PRINT(STR("JAMMATestScreen.noInputs"));
}
*(--ptr) = 0;
//LOG("remaining=%d", end - ptr);
//TextScreen::update(ctx);
if (ctx.buttons.longPressed(ui::BTN_START))
ctx.show(APP->_testMenuScreen, true, true);
}
struct AudioTestEntry {
public:
util::Hash name;
void (AudioTestScreen::*target)(ui::Context &ctx);
};
static const AudioTestEntry _AUDIO_TEST_ENTRIES[]{
{
.name = "AudioTestScreen.playLeft"_h,
.target = &AudioTestScreen::playLeft
}, {
.name = "AudioTestScreen.playRight"_h,
.target = &AudioTestScreen::playRight
}, {
.name = "AudioTestScreen.playBoth"_h,
.target = &AudioTestScreen::playBoth
}, {
.name = "AudioTestScreen.enableAmp"_h,
.target = &AudioTestScreen::enableAmp
}, {
.name = "AudioTestScreen.disableAmp"_h,
.target = &AudioTestScreen::disableAmp
}, {
.name = "AudioTestScreen.enableCDDA"_h,
.target = &AudioTestScreen::enableCDDA
}, {
.name = "AudioTestScreen.disableCDDA"_h,
.target = &AudioTestScreen::disableCDDA
}
};
const char *AudioTestScreen::_getItemName(ui::Context &ctx, int index) const {
return STRH(_AUDIO_TEST_ENTRIES[index].name);
}
void AudioTestScreen::playLeft(ui::Context &ctx) {
ctx.sounds[ui::SOUND_STARTUP].play(spu::MAX_VOLUME, 0);
}
void AudioTestScreen::playRight(ui::Context &ctx) {
ctx.sounds[ui::SOUND_STARTUP].play(0, spu::MAX_VOLUME);
}
void AudioTestScreen::playBoth(ui::Context &ctx) {
ctx.sounds[ui::SOUND_STARTUP].play();
}
void AudioTestScreen::enableAmp(ui::Context &ctx) {
io::setMiscOutput(io::MISC_AMP_ENABLE, true);
}
void AudioTestScreen::disableAmp(ui::Context &ctx) {
io::setMiscOutput(io::MISC_AMP_ENABLE, false);
}
void AudioTestScreen::enableCDDA(ui::Context &ctx) {
io::setMiscOutput(io::MISC_CDDA_ENABLE, true);
}
void AudioTestScreen::disableCDDA(ui::Context &ctx) {
io::setMiscOutput(io::MISC_CDDA_ENABLE, false);
}
void AudioTestScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("AudioTestScreen.title");
_prompt = STR("AudioTestScreen.prompt");
_itemPrompt = STR("AudioTestScreen.itemPrompt");
_listLength = util::countOf(_AUDIO_TEST_ENTRIES);
ListScreen::show(ctx, goBack);
}
void AudioTestScreen::update(ui::Context &ctx) {
ListScreen::update(ctx);
if (ctx.buttons.pressed(ui::BTN_START)) {
if (ctx.buttons.held(ui::BTN_LEFT) || ctx.buttons.held(ui::BTN_RIGHT)) {
io::setMiscOutput(io::MISC_AMP_ENABLE, false);
io::setMiscOutput(io::MISC_CDDA_ENABLE, false);
ctx.show(APP->_testMenuScreen, true, true);
} else {
(this->*_AUDIO_TEST_ENTRIES[_activeItem].target)(ctx);
}
}
}
/* Base test pattern screen class */
static constexpr gpu::Color _BACKGROUND_COLOR = 0x000000;
static constexpr gpu::Color _FOREGROUND_COLOR = 0xffffff;
void TestPatternScreen::_drawTextOverlay(
ui::Context &ctx, const char *title, const char *prompt
) const {
int screenWidth = ctx.gpuCtx.width - ui::SCREEN_MARGIN_X * 2;
int screenHeight = ctx.gpuCtx.height - ui::SCREEN_MARGIN_Y * 2;
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
gpu::RectWH backdropRect;
backdropRect.x = ui::SCREEN_MARGIN_X - ui::SHADOW_OFFSET;
backdropRect.y = ui::SCREEN_MARGIN_Y - ui::SHADOW_OFFSET;
backdropRect.w = ui::SHADOW_OFFSET * 2 + screenWidth;
backdropRect.h = ui::SHADOW_OFFSET * 2 + ctx.font.metrics.lineHeight;
ctx.gpuCtx.drawRect(backdropRect, ctx.colors[ui::COLOR_SHADOW], true);
backdropRect.y += screenHeight - ui::SCREEN_PROMPT_HEIGHT_MIN;
ctx.gpuCtx.drawRect(backdropRect, ctx.colors[ui::COLOR_SHADOW], true);
gpu::Rect textRect;
textRect.x1 = ui::SCREEN_MARGIN_X;
textRect.y1 = ui::SCREEN_MARGIN_Y;
textRect.x2 = textRect.x1 + screenWidth;
textRect.y2 = textRect.y1 + ctx.font.metrics.lineHeight;
ctx.font.draw(ctx.gpuCtx, title, textRect, ctx.colors[ui::COLOR_TITLE]);
textRect.y1 += screenHeight - ui::SCREEN_PROMPT_HEIGHT_MIN;
textRect.y2 += textRect.y1;
ctx.font.draw(
ctx.gpuCtx, prompt, textRect, ctx.colors[ui::COLOR_TEXT1], true
);
}
void TestPatternScreen::draw(ui::Context &ctx, bool active) const {
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
ctx.gpuCtx.drawRect(
0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height, _BACKGROUND_COLOR
);
}
void TestPatternScreen::update(ui::Context &ctx) {
if (ctx.buttons.pressed(ui::BTN_START))
ctx.show(APP->_testMenuScreen, true, true);
}
/* Color intensity test screen */
struct IntensityBar {
public:
util::Hash name;
gpu::Color color;
};
static constexpr int _INTENSITY_BAR_NAME_WIDTH = 32;
static constexpr int _INTENSITY_BAR_WIDTH = 256;
static constexpr int _INTENSITY_BAR_HEIGHT = 32;
static const IntensityBar _INTENSITY_BARS[]{
{
.name = "ColorIntensityScreen.white"_h,
.color = 0xffffff
}, {
.name = "ColorIntensityScreen.red"_h,
.color = 0x0000ff
}, {
.name = "ColorIntensityScreen.green"_h,
.color = 0x00ff00
}, {
.name = "ColorIntensityScreen.blue"_h,
.color = 0xff0000
}
};
void ColorIntensityScreen::draw(ui::Context &ctx, bool active) const {
TestPatternScreen::draw(ctx, active);
int barWidth = _INTENSITY_BAR_NAME_WIDTH + _INTENSITY_BAR_WIDTH;
int barHeight = _INTENSITY_BAR_HEIGHT * util::countOf(_INTENSITY_BARS);
int offsetX = (ctx.gpuCtx.width - barWidth) / 2;
int offsetY = (ctx.gpuCtx.height - barHeight) / 2;
gpu::RectWH textRect, barRect;
textRect.x = offsetX;
textRect.y =
offsetY + (_INTENSITY_BAR_HEIGHT - ctx.font.metrics.lineHeight) / 2;
textRect.w = _INTENSITY_BAR_NAME_WIDTH;
textRect.h = ctx.font.metrics.lineHeight;
barRect.x = offsetX + _INTENSITY_BAR_NAME_WIDTH;
barRect.y = offsetY;
barRect.w = _INTENSITY_BAR_WIDTH;
barRect.h = _INTENSITY_BAR_HEIGHT / 2;
for (auto &bar : _INTENSITY_BARS) {
ctx.font.draw(
ctx.gpuCtx, STRH(bar.name), textRect, ctx.colors[ui::COLOR_TEXT1]
);
textRect.y += _INTENSITY_BAR_HEIGHT;
ctx.gpuCtx.setTexturePage(0, false);
ctx.gpuCtx.drawGradientRectH(barRect, _BACKGROUND_COLOR, bar.color);
barRect.y += _INTENSITY_BAR_HEIGHT / 2;
ctx.gpuCtx.setTexturePage(0, true);
ctx.gpuCtx.drawGradientRectH(barRect, _BACKGROUND_COLOR, bar.color);
barRect.y += _INTENSITY_BAR_HEIGHT / 2;
}
char value[2]{ 0, 0 };
textRect.x = barRect.x + 1;
textRect.y = offsetY - ctx.font.metrics.lineHeight;
textRect.w = _INTENSITY_BAR_WIDTH / 32;
for (int i = 0; i < 32; i++, textRect.x += textRect.w) {
value[0] = util::HEX_CHARSET[i & 15];
ctx.font.draw(ctx.gpuCtx, value, textRect, ctx.colors[ui::COLOR_TEXT2]);
}
_drawTextOverlay(
ctx, STR("ColorIntensityScreen.title"),
STR("ColorIntensityScreen.prompt")
);
}
/* Geometry test screen */
static constexpr int _GRID_CELL_SIZE = 16;
void GeometryScreen::draw(ui::Context &ctx, bool active) const {
TestPatternScreen::draw(ctx, active);
for (int x = -1; x < ctx.gpuCtx.width; x += _GRID_CELL_SIZE)
ctx.gpuCtx.drawRect(
x, 0, 2, ctx.gpuCtx.height, ctx.colors[ui::COLOR_TEXT1]
);
for (int y = -1; y < ctx.gpuCtx.height; y += _GRID_CELL_SIZE)
ctx.gpuCtx.drawRect(
0, y, ctx.gpuCtx.width, 2, ctx.colors[ui::COLOR_TEXT1]
);
int offset = (_GRID_CELL_SIZE / 2) - 1;
int rightOffset = ctx.gpuCtx.width - (offset + 2);
int bottomOffset = ctx.gpuCtx.height - (offset + 2);
for (int x = offset; x <= rightOffset; x += _GRID_CELL_SIZE) {
for (int y = offset; y <= bottomOffset; y += _GRID_CELL_SIZE) {
auto color = (
(x == offset) || (y == offset) || (x == rightOffset) ||
(y == bottomOffset)
)
? ctx.colors[ui::COLOR_ACCENT1]
: _FOREGROUND_COLOR;
ctx.gpuCtx.drawRect(x, y, 2, 2, color);
}
}
_drawTextOverlay(
ctx, STR("GeometryScreen.title"), STR("GeometryScreen.prompt")
);
}

72
src/main/app/tests.hpp Normal file
View File

@ -0,0 +1,72 @@
#pragma once
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
/* Top-level test menu */
class TestMenuScreen : public ui::ListScreen {
protected:
const char *_getItemName(ui::Context &ctx, int index) const;
public:
void jammaTest(ui::Context &ctx);
void audioTest(ui::Context &ctx);
void colorIntensity(ui::Context &ctx);
void geometry(ui::Context &ctx);
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
/* Test submenus */
class JAMMATestScreen : public ui::TextScreen {
private:
char _bodyText[2048];
public:
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class AudioTestScreen : public ui::ListScreen {
protected:
const char *_getItemName(ui::Context &ctx, int index) const;
public:
void playLeft(ui::Context &ctx);
void playRight(ui::Context &ctx);
void playBoth(ui::Context &ctx);
void enableAmp(ui::Context &ctx);
void disableAmp(ui::Context &ctx);
void enableCDDA(ui::Context &ctx);
void disableCDDA(ui::Context &ctx);
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
/* Test pattern screens */
class TestPatternScreen : public ui::AnimatedScreen {
protected:
void _drawTextOverlay(
ui::Context &ctx, const char *title, const char *prompt
) const;
public:
virtual void draw(ui::Context &ctx, bool active) const;
virtual void update(ui::Context &ctx);
};
class ColorIntensityScreen : public TestPatternScreen {
public:
void draw(ui::Context &ctx, bool active) const;
};
class GeometryScreen : public TestPatternScreen {
public:
void draw(ui::Context &ctx, bool active) const;
};