Merge pull request #4011 from iamgreaser/gm/video-debug-device
Add initial "Unit Tester" device for analysing framebuffer contents
This commit is contained in:
267
doc/specifications/86box-unit-tester.md
Normal file
267
doc/specifications/86box-unit-tester.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# 86Box Unit Tester device specification v1.0.0
|
||||
|
||||
By GreaseMonkey + other 86Box contributors, 2024.
|
||||
This specification, including any code samples included, has been released into the Public Domain under the Creative Commons CC0 licence version 1.0 or later, as described here: <http://creativecommons.org/publicdomain/zero/1.0>
|
||||
|
||||
The 86Box Unit Tester is a facility for allowing one to unit-test various parts of 86Box's emulation which would otherwise not be exposed to the emulated system.
|
||||
|
||||
The original purpose of this was to make it possible to analyse and verify aspects of the monitor framebuffers in order to detect and prevent regressions in certain pieces of video hardware.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
## Versioning
|
||||
|
||||
This specification follows the rules of Semantic Versioning 2.0.0 as documented here: <https://semver.org/spec/v2.0.0.html>
|
||||
|
||||
The format is `major.minor.patch`.
|
||||
|
||||
- Before you mess with this specification, talk to the other contributors first!
|
||||
- Any changes need to be tracked in the Version History below, mostly in the event that this document escapes into the wild and doesn't have the Git history attached to it.
|
||||
- If it clarifies something without introducing any behaviour changes (e.g. formatting changes, spelling fixes), increment the patch version.
|
||||
- If it introduces a backwards-compatible change, increment the minor version and reset the patch version to 0.
|
||||
- If it introduces a backwards-incompatible change, increment the major version and reset the minor and patch versions to 0.
|
||||
- If you make a mistake and accidentally introduce a backward-incompatible change, fix the mistake and increment the minor version.
|
||||
- To clarify, modifications to *this* section are to be classified as a *patch* version update.
|
||||
- If you understand SemVer 2.0.0, you may also do other things to the version number according to the specification.
|
||||
|
||||
And lastly, the 3 golden rules of protocol specifications:
|
||||
|
||||
1. If it's not documented, it doesn't exist.
|
||||
2. If it's not documented but somehow exists, it's a bug.
|
||||
3. If it's a bug, it needs to be fixed. (Yes, I'm talking to you. You who introduced the bug. Go fix it.)
|
||||
|
||||
The checklist:
|
||||
|
||||
- Work out what kind of version number this document needs.
|
||||
- Update the version number at the top of the file.
|
||||
- Add an entry to the "Version History" section below describing roughly what was changed.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
## Version History
|
||||
|
||||
Dates are based on what day it was in UTC at the time of publication.
|
||||
|
||||
New entries are placed at the top. That is, immediately following this paragraph.
|
||||
|
||||
### v1.0.0 (2024-01-08)
|
||||
Initial release. Authored by GreaseMonkey.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
## Conventions
|
||||
|
||||
### Integer types
|
||||
|
||||
- `i8` denotes a signed 8-bit value.
|
||||
- `u8` denotes an unsigned 8-bit value.
|
||||
- `w8` denotes an 8-bit value which wraps around.
|
||||
- `x8` denotes an 8-bit value where the signedness is irrelevant.
|
||||
- `e8` ("either") denotes an 8-bit value where the most significant bit is clear - in effect, this is a 7-bit unsigned value, and can be interepreted identically as a signed 8-bit value.
|
||||
- `u16L` denotes a little-endian unsigned 16-bit value.
|
||||
- `u16B` would denote a big-endian unsigned 16-bit value if we had any big-endian values.
|
||||
- `[N]T` denotes an array of `N` values of type `T`, whatever `N` and `T` are.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
## Usage
|
||||
|
||||
### Accessing the device and configuring the I/O base address
|
||||
|
||||
Find an area in I/O space where 2 addresses are confirmed (or assumed) to be unused.
|
||||
There is no need for the 2 addresses to be 2-byte-aligned.
|
||||
|
||||
Send the following sequence of bytes to port 0x80 with INTERRUPTS DISABLED:
|
||||
|
||||
'8', '6', 'B', 'o', 'x', (IOBASE & 0xFF), (IOBASE >> 8)
|
||||
|
||||
Alternatively denoted in hex:
|
||||
|
||||
38 36 42 6F 78 yy xx
|
||||
|
||||
There are no timing constraints. This is an emulator, after all.
|
||||
|
||||
To confirm that this has happened, read the status port at IOBASE+0x00.
|
||||
If it's 0xFF, then the device is most likely not present.
|
||||
Otherwise, one can potentially assume that it exists and has been configured successfully.
|
||||
(You *did* make sure that the space was unused *before* doing this, right?)
|
||||
|
||||
IOBASE is allowed to overlap the trigger port, but please don't do this!
|
||||
|
||||
### Hiding the device
|
||||
|
||||
Set the I/O base address to 0xFFFF using the above method.
|
||||
|
||||
### Executing commands
|
||||
|
||||
The ports at IOBASE+0x00 and IOBASE+0x01 are all 8 bits wide.
|
||||
|
||||
Writing to IOBASE+0x00 cancels any in-flight commands and sends a new command.
|
||||
|
||||
Reading from IOBASE+0x00 reads the status:
|
||||
|
||||
- bit 0: There is data to be read from this device
|
||||
- If one reads with this bit clear, the returned data will be 0xFF.
|
||||
- bit 1: The device is expecting data to be sent to it
|
||||
- If one writes with this bit clear, the data will be ignored.
|
||||
- bit 2: There is no command in flight
|
||||
- If this is set, then bits 0 and 1 will be clear.
|
||||
- bit 3: The previously-sent command does not exist.
|
||||
- bits 4 .. 7: Reserved, should be 0.
|
||||
|
||||
Writing to IOBASE+0x01 provides data to the device if said data is needed.
|
||||
|
||||
Reading from IOBASE+0x01 fetches the next byte data to the device if said data is needed.
|
||||
|
||||
### General flow of executing a command:
|
||||
|
||||
This is how most commands will work.
|
||||
|
||||
- Write the command to IOBASE+0x00.
|
||||
- If data needs to be written or read:
|
||||
- Read the status from IOBASE+0x00 and confirm that bit 2 is clear.
|
||||
If it is set, then the command may not exist.
|
||||
Check bit 3 if that's the case.
|
||||
- If data needs to be written:
|
||||
- Write all the data one needs to write.
|
||||
- If data needs to be read:
|
||||
- Read the status from IOBASE+0x00 and wait until bit 0 is set.
|
||||
If it is set, then the command may not exist.
|
||||
Check bit 3 if that's the case.
|
||||
- Keep reading bytes until one is satisfied.
|
||||
- Otherwise:
|
||||
- Read the status from IOBASE+0x00 and wait until any of the bottom 3 bits are set.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
## Command reference
|
||||
|
||||
### 0x00: No-op
|
||||
|
||||
This does nothing, takes no input, and gives no output.
|
||||
|
||||
This is an easy way to reset the status to 0x04 (no command in flight, not waiting for reads or writes, and no errors).
|
||||
|
||||
### 0x01: Capture Screen Snapshot
|
||||
|
||||
Captures a snapshot of the current screen state and stores it in the current snapshot buffer.
|
||||
|
||||
The initial state of the screen snapshot buffer has an image area of 0x0, an overscanned area of 0x0, and an image start offset of (0,0).
|
||||
|
||||
Input:
|
||||
|
||||
* u8 monitor
|
||||
- 0x00 = no monitor - clear the screen snapshot
|
||||
- 0x01 = primary monitor
|
||||
- 0x02 = secondary monitor
|
||||
- Any monitor which is not available is treated as 0x00, and clears the screen snapshot.
|
||||
|
||||
Output:
|
||||
|
||||
* `e16L` image width in pixels
|
||||
* `e16L` image height in pixels
|
||||
* `e16L` overscanned width in pixels
|
||||
* `e16L` overscanned height in pixels
|
||||
* `e16L` X offset of image start
|
||||
* `e16L` Y offset of image start
|
||||
|
||||
If there is no screen snapshot, then all values will be 0 as per the initial screen snapshot buffer state.
|
||||
|
||||
### 0x02: Read Screen Snapshot Rectangle
|
||||
|
||||
Returns a rectangular snapshot of the screen snapshot buffer as an array of 32bpp 8:8:8:8 B:G:R:X pixels.
|
||||
|
||||
Input:
|
||||
|
||||
* `e16L` w: rectangle width in pixels
|
||||
* `e16L` h: rectangle height in pixels
|
||||
* `i16L` x: X offset relative to image start
|
||||
* `i16L` y: Y offset relative to image start
|
||||
|
||||
Output:
|
||||
|
||||
* `[h][w][4]u8`: image data
|
||||
- `[y][x][0]` is the blue component, or 0x00 if the pixel is outside the snapshot area.
|
||||
- `[y][x][1]` is the green component, or 0x00 if the pixel is outside the snapshot area.
|
||||
- `[y][x][2]` is the red component, or 0x00 if the pixel is outside the snapshot area.
|
||||
- `[y][x][3]` is 0x00, or 0xFF if the pixel is outside the snapshot area.
|
||||
|
||||
### 0x03: Verify Screen Snapshot Rectangle
|
||||
|
||||
As per 0x02 "Read Screen Snapshot Rectangle", except instead of returning the pixel data, it returns a CRC-32 of the data.
|
||||
|
||||
The CRC is as per zlib's `crc32()` function. Specifically, one uses a right-shifting Galois LFSR with a polynomial of 0xEDB88320, bytes XORed against the least significant byte, the initial seed is 0xFFFFFFFF, and all bits of the output are inverted.
|
||||
|
||||
(Rationale: There are better CRCs, but this one is ubiquitous and still really good... and we don't need to protect against deliberate tampering.)
|
||||
|
||||
Input:
|
||||
|
||||
* `e16L` w: rectangle width in pixels
|
||||
* `e16L` h: rectangle height in pixels
|
||||
* `i16L` x: X offset relative to image start
|
||||
* `i16L` y: Y offset relative to image start
|
||||
|
||||
Output:
|
||||
|
||||
* `u32L` crc: CRC-32 of rectangle data
|
||||
|
||||
### 0x04: Exit 86Box
|
||||
|
||||
Exits 86Box, unless this command is disabled.
|
||||
|
||||
- If the command is enabled, then program execution terminates immediately.
|
||||
- If the command is disabled, it still counts as having executed correctly, but program execution continues. This makes it useful to show a "results" screen for a unit test.
|
||||
|
||||
Input:
|
||||
|
||||
* u8 exit code:
|
||||
- The actual exit code is clamped to no greater than the maximum valid exit code.
|
||||
- In practice, this is probably going to be 0x7F.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
## Implementation notes
|
||||
|
||||
### Port 0x80 sequence detection
|
||||
|
||||
In order to ensure that one can always trigger the activation sequence, there are effectively two finite state machines in action.
|
||||
|
||||
FSM1:
|
||||
- Wait for 8.
|
||||
- Wait for 6.
|
||||
- Wait for B.
|
||||
- Wait for o.
|
||||
- Wait for x.
|
||||
Once received, set FSM2 to "Wait for low byte",
|
||||
then go back to "Wait for 8".
|
||||
|
||||
If at any point an 8 arrives, jump to the "Wait for 6" step.
|
||||
|
||||
Otherwise, if any other unexpected byte arrives, jump to the "Wait for 8" step.
|
||||
|
||||
FSM2:
|
||||
- Idle.
|
||||
- Wait for low byte. Once received, store this in a temporary location.
|
||||
- Wait for high byte.
|
||||
Once received, replace IOBASE with this byte in the high byte and the temporary value in the low byte,
|
||||
then go back to "Idle".
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
## Extending the protocol
|
||||
|
||||
### Adding new commands
|
||||
|
||||
Commands 0x01 through 0x7F accept a single command byte.
|
||||
|
||||
Command bytes 0x80 through 0xFB are reserved for 16-bit command IDs, to be written in a similar way to this:
|
||||
|
||||
- Write the first command byte (0x80 through 0xFF) to the command register.
|
||||
- If this block of commands does not exist, then the command is cancelled and the status is set to 0x0C.
|
||||
- Otherwise, the status is set to 0x0
|
||||
- Write the next command byte (0x00 through 0xFF) to the data register.
|
||||
- If this block of commands does not exist, then the command is cancelled and the status is set to 0x0C.
|
||||
- Otherwise, the command exists and the status is set according to the command.
|
||||
|
||||
Command bytes 0xFC through 0xFF are reserved for if we somehow need more than 16 bits worth of command ID.
|
||||
|
@@ -65,6 +65,7 @@
|
||||
#include <86box/machine.h>
|
||||
#include <86box/bugger.h>
|
||||
#include <86box/postcard.h>
|
||||
#include <86box/unittester.h>
|
||||
#include <86box/isamem.h>
|
||||
#include <86box/isartc.h>
|
||||
#include <86box/lpt.h>
|
||||
@@ -173,6 +174,7 @@ bool serial_passthrough_enabled[SERIAL_MAX] = { 0, 0, 0, 0 }; /* (C) activat
|
||||
pass-through for serial ports */
|
||||
int bugger_enabled = 0; /* (C) enable ISAbugger */
|
||||
int postcard_enabled = 0; /* (C) enable POST card */
|
||||
int unittester_enabled = 0; /* (C) enable unit tester device */
|
||||
int isamem_type[ISAMEM_MAX] = { 0, 0, 0, 0 }; /* (C) enable ISA mem cards */
|
||||
int isartc_type = 0; /* (C) enable ISA RTC card */
|
||||
int gfxcard[2] = { 0, 0 }; /* (C) graphics/video card */
|
||||
@@ -1221,6 +1223,8 @@ pc_reset_hard_init(void)
|
||||
device_add(&bugger_device);
|
||||
if (postcard_enabled)
|
||||
device_add(&postcard_device);
|
||||
if (unittester_enabled)
|
||||
device_add(&unittester_device);
|
||||
|
||||
if (IS_ARCH(machine, MACHINE_BUS_PCI)) {
|
||||
pci_register_cards();
|
||||
|
10
src/config.c
10
src/config.c
@@ -1514,8 +1514,9 @@ load_other_peripherals(void)
|
||||
char *p;
|
||||
char temp[512];
|
||||
|
||||
bugger_enabled = !!ini_section_get_int(cat, "bugger_enabled", 0);
|
||||
postcard_enabled = !!ini_section_get_int(cat, "postcard_enabled", 0);
|
||||
bugger_enabled = !!ini_section_get_int(cat, "bugger_enabled", 0);
|
||||
postcard_enabled = !!ini_section_get_int(cat, "postcard_enabled", 0);
|
||||
unittester_enabled = !!ini_section_get_int(cat, "unittester_enabled", 0);
|
||||
|
||||
for (uint8_t c = 0; c < ISAMEM_MAX; c++) {
|
||||
sprintf(temp, "isamem%d_type", c);
|
||||
@@ -2348,6 +2349,11 @@ save_other_peripherals(void)
|
||||
else
|
||||
ini_section_set_int(cat, "postcard_enabled", postcard_enabled);
|
||||
|
||||
if (unittester_enabled == 0)
|
||||
ini_section_delete_var(cat, "unittester_enabled");
|
||||
else
|
||||
ini_section_set_int(cat, "unittester_enabled", unittester_enabled);
|
||||
|
||||
for (uint8_t c = 0; c < ISAMEM_MAX; c++) {
|
||||
sprintf(temp, "isamem%d_type", c);
|
||||
if (isamem_type[c] == 0)
|
||||
|
@@ -17,7 +17,7 @@
|
||||
|
||||
add_library(dev OBJECT bugger.c cassette.c cartridge.c hasp.c hwm.c hwm_lm75.c hwm_lm78.c hwm_gl518sm.c
|
||||
hwm_vt82c686.c ibm_5161.c isamem.c isartc.c ../lpt.c pci_bridge.c
|
||||
postcard.c serial.c clock_ics9xxx.c isapnp.c i2c.c i2c_gpio.c
|
||||
postcard.c serial.c unittester.c clock_ics9xxx.c isapnp.c i2c.c i2c_gpio.c
|
||||
smbus_piix4.c smbus_ali7101.c keyboard.c keyboard_xt.c
|
||||
kbc_at.c kbc_at_dev.c
|
||||
keyboard_at.c
|
||||
|
626
src/device/unittester.c
Normal file
626
src/device/unittester.c
Normal file
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Debug device for assisting in unit testing.
|
||||
* See doc/specifications/86box-unit-tester.md for more info.
|
||||
* If modifying the protocol, you MUST modify the specification
|
||||
* and increment the version number.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: GreaseMonkey, <thematrixeatsyou+86b@gmail.com>
|
||||
*
|
||||
* Copyright 2024 GreaseMonkey.
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
#define HAVE_STDARG_H
|
||||
#include <86box/86box.h>
|
||||
#include <86box/io.h>
|
||||
#include <86box/plat.h>
|
||||
#include <86box/unittester.h>
|
||||
#include <86box/video.h>
|
||||
|
||||
enum fsm1_value {
|
||||
UT_FSM1_WAIT_8,
|
||||
UT_FSM1_WAIT_6,
|
||||
UT_FSM1_WAIT_B,
|
||||
UT_FSM1_WAIT_o,
|
||||
UT_FSM1_WAIT_x,
|
||||
};
|
||||
enum fsm2_value {
|
||||
UT_FSM2_IDLE,
|
||||
UT_FSM2_WAIT_IOBASE_0,
|
||||
UT_FSM2_WAIT_IOBASE_1,
|
||||
};
|
||||
|
||||
/* Status bit mask */
|
||||
#define UT_STATUS_AWAITING_READ (1 << 0)
|
||||
#define UT_STATUS_AWAITING_WRITE (1 << 1)
|
||||
#define UT_STATUS_IDLE (1 << 2)
|
||||
#define UT_STATUS_UNSUPPORTED_CMD (1 << 3)
|
||||
|
||||
/* Command list */
|
||||
enum unittester_cmd {
|
||||
UT_CMD_NOOP = 0x00,
|
||||
UT_CMD_CAPTURE_SCREEN_SNAPSHOT = 0x01,
|
||||
UT_CMD_READ_SCREEN_SNAPSHOT_RECTANGLE = 0x02,
|
||||
UT_CMD_VERIFY_SCREEN_SNAPSHOT_RECTANGLE = 0x03,
|
||||
UT_CMD_EXIT = 0x04,
|
||||
};
|
||||
|
||||
struct unittester_state {
|
||||
/* I/O port settings */
|
||||
uint16_t trigger_port;
|
||||
uint16_t iobase_port;
|
||||
|
||||
/* Trigger port finite state machines */
|
||||
/* FSM1: "86Box" string detection */
|
||||
enum fsm1_value fsm1;
|
||||
/* FSM2: IOBASE port selection, once trigger is activated */
|
||||
enum fsm2_value fsm2;
|
||||
uint16_t fsm2_new_iobase;
|
||||
|
||||
/* Command and data handling state */
|
||||
uint8_t status;
|
||||
enum unittester_cmd cmd_id;
|
||||
uint32_t write_offs;
|
||||
uint32_t write_len;
|
||||
uint64_t read_offs;
|
||||
uint64_t read_len;
|
||||
|
||||
/* Screen snapshot state */
|
||||
/* Monitor to take snapshot on */
|
||||
uint8_t snap_monitor;
|
||||
/* Main image width + height */
|
||||
uint16_t snap_img_width;
|
||||
uint16_t snap_img_height;
|
||||
/* Fully overscanned image width + height */
|
||||
uint16_t snap_overscan_width;
|
||||
uint16_t snap_overscan_height;
|
||||
/* Offset of actual image within overscanned area */
|
||||
uint16_t snap_img_xoffs;
|
||||
uint16_t snap_img_yoffs;
|
||||
|
||||
/* Command-specific state */
|
||||
/* 0x02: Read Screen Snapshot Rectangle */
|
||||
/* 0x03: Verify Screen Snapshot Rectangle */
|
||||
uint16_t read_snap_width;
|
||||
uint16_t read_snap_height;
|
||||
int16_t read_snap_xoffs;
|
||||
int16_t read_snap_yoffs;
|
||||
uint32_t read_snap_crc;
|
||||
|
||||
/* 0x04: Exit */
|
||||
uint8_t exit_code;
|
||||
};
|
||||
static struct unittester_state unittester;
|
||||
static const struct unittester_state unittester_defaults = {
|
||||
.trigger_port = 0x0080,
|
||||
.iobase_port = 0xFFFF,
|
||||
.fsm1 = UT_FSM1_WAIT_8,
|
||||
.fsm2 = UT_FSM2_IDLE,
|
||||
.status = UT_STATUS_IDLE,
|
||||
.cmd_id = UT_CMD_NOOP,
|
||||
};
|
||||
|
||||
static const device_config_t unittester_config[] = {
|
||||
{ .name = "exit_enabled",
|
||||
.description = "Enable 0x04 \"Exit 86Box\" command",
|
||||
.type = CONFIG_BINARY,
|
||||
.default_int = 1,
|
||||
.default_string = "" },
|
||||
{ .type = CONFIG_END }
|
||||
};
|
||||
|
||||
/* Kept separate, as we will be reusing this object */
|
||||
static bitmap_t *unittester_screen_buffer = NULL;
|
||||
|
||||
static bool unittester_exit_enabled = true;
|
||||
|
||||
#ifdef ENABLE_UNITTESTER_LOG
|
||||
int unittester_do_log = ENABLE_UNITTESTER_LOG;
|
||||
|
||||
static void
|
||||
unittester_log(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
if (unittester_do_log) {
|
||||
va_start(ap, fmt);
|
||||
pclog_ex(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
#else
|
||||
# define unittester_log(fmt, ...)
|
||||
#endif
|
||||
|
||||
static uint8_t
|
||||
unittester_read_snap_rect_idx(uint64_t offs)
|
||||
{
|
||||
/* WARNING: If the width is somehow 0 and wasn't caught earlier, you'll probably get a divide by zero crash. */
|
||||
uint32_t idx = (offs & 0x3);
|
||||
int64_t x = (offs >> 2) % unittester.read_snap_width;
|
||||
int64_t y = (offs >> 2) / unittester.read_snap_width;
|
||||
x += unittester.read_snap_xoffs;
|
||||
y += unittester.read_snap_yoffs;
|
||||
|
||||
if (x < 0 || y < 0 || x >= unittester.snap_overscan_width || y >= unittester.snap_overscan_height) {
|
||||
/* Out of range! */
|
||||
return (idx == 3 ? 0xFF : 0x00);
|
||||
} else {
|
||||
/* In range */
|
||||
return (unittester_screen_buffer->line[y][x] & 0x00FFFFFF) >> (idx * 8);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
unittester_write(uint16_t port, uint8_t val, UNUSED(void *priv))
|
||||
{
|
||||
if (port == unittester.iobase_port + 0x00) {
|
||||
/* Command port */
|
||||
unittester_log("[UT] W %02X Command\n", val);
|
||||
|
||||
unittester.write_offs = 0;
|
||||
unittester.write_len = 0;
|
||||
unittester.read_offs = 0;
|
||||
unittester.read_len = 0;
|
||||
|
||||
switch (val) {
|
||||
/* 0x00: No-op */
|
||||
case UT_CMD_NOOP:
|
||||
unittester.cmd_id = UT_CMD_NOOP;
|
||||
unittester.status = UT_STATUS_IDLE;
|
||||
break;
|
||||
|
||||
/* 0x01: Capture Screen Snapshot */
|
||||
case UT_CMD_CAPTURE_SCREEN_SNAPSHOT:
|
||||
unittester.cmd_id = UT_CMD_CAPTURE_SCREEN_SNAPSHOT;
|
||||
unittester.status = UT_STATUS_AWAITING_WRITE;
|
||||
unittester.write_len = 1;
|
||||
break;
|
||||
|
||||
/* 0x02: Read Screen Snapshot Rectangle */
|
||||
case UT_CMD_READ_SCREEN_SNAPSHOT_RECTANGLE:
|
||||
unittester.cmd_id = UT_CMD_READ_SCREEN_SNAPSHOT_RECTANGLE;
|
||||
unittester.status = UT_STATUS_AWAITING_WRITE;
|
||||
unittester.write_len = 8;
|
||||
break;
|
||||
|
||||
/* 0x03: Verify Screen Snapshot Rectangle */
|
||||
case UT_CMD_VERIFY_SCREEN_SNAPSHOT_RECTANGLE:
|
||||
unittester.cmd_id = UT_CMD_VERIFY_SCREEN_SNAPSHOT_RECTANGLE;
|
||||
unittester.status = UT_STATUS_AWAITING_WRITE;
|
||||
unittester.write_len = 8;
|
||||
break;
|
||||
|
||||
/* 0x04: Exit */
|
||||
case UT_CMD_EXIT:
|
||||
unittester.cmd_id = UT_CMD_EXIT;
|
||||
unittester.status = UT_STATUS_AWAITING_WRITE;
|
||||
unittester.write_len = 1;
|
||||
break;
|
||||
|
||||
/* Unsupported command - terminate here */
|
||||
default:
|
||||
unittester.cmd_id = UT_CMD_NOOP;
|
||||
unittester.status = UT_STATUS_IDLE | UT_STATUS_UNSUPPORTED_CMD;
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (port == unittester.iobase_port + 0x01) {
|
||||
/* Data port */
|
||||
unittester_log("[UT] W %02X Data\n", val);
|
||||
|
||||
/* Skip if not awaiting */
|
||||
if ((unittester.status & UT_STATUS_AWAITING_WRITE) == 0)
|
||||
return;
|
||||
|
||||
switch (unittester.cmd_id) {
|
||||
case UT_CMD_EXIT:
|
||||
switch (unittester.write_offs) {
|
||||
case 0:
|
||||
unittester.exit_code = val;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case UT_CMD_CAPTURE_SCREEN_SNAPSHOT:
|
||||
switch (unittester.write_offs) {
|
||||
case 0:
|
||||
unittester.snap_monitor = val;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case UT_CMD_READ_SCREEN_SNAPSHOT_RECTANGLE:
|
||||
case UT_CMD_VERIFY_SCREEN_SNAPSHOT_RECTANGLE:
|
||||
switch (unittester.write_offs) {
|
||||
case 0:
|
||||
unittester.read_snap_width = (uint16_t) val;
|
||||
break;
|
||||
case 1:
|
||||
unittester.read_snap_width |= ((uint16_t) val) << 8;
|
||||
break;
|
||||
case 2:
|
||||
unittester.read_snap_height = (uint16_t) val;
|
||||
break;
|
||||
case 3:
|
||||
unittester.read_snap_height |= ((uint16_t) val) << 8;
|
||||
break;
|
||||
case 4:
|
||||
unittester.read_snap_xoffs = (uint16_t) val;
|
||||
break;
|
||||
case 5:
|
||||
unittester.read_snap_xoffs |= ((uint16_t) val) << 8;
|
||||
break;
|
||||
case 6:
|
||||
unittester.read_snap_yoffs = (uint16_t) val;
|
||||
break;
|
||||
case 7:
|
||||
unittester.read_snap_yoffs |= ((uint16_t) val) << 8;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* This should not be reachable, but just in case... */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Advance write buffer */
|
||||
unittester.write_offs += 1;
|
||||
if (unittester.write_offs >= unittester.write_len) {
|
||||
unittester.status &= ~UT_STATUS_AWAITING_WRITE;
|
||||
/* Determine what we're doing here based on the command. */
|
||||
switch (unittester.cmd_id) {
|
||||
case UT_CMD_EXIT:
|
||||
unittester_log("[UT] Exit received - code = %02X\n", unittester.exit_code);
|
||||
|
||||
/* CHECK: Do we actually exit? */
|
||||
if (unittester_exit_enabled) {
|
||||
/* Yes - call exit! */
|
||||
/* Clamp exit code */
|
||||
if (unittester.exit_code > 0x7F)
|
||||
unittester.exit_code = 0x7F;
|
||||
|
||||
/* Exit somewhat quickly! */
|
||||
unittester_log("[UT] Exit enabled, exiting with code %02X\n", unittester.exit_code);
|
||||
exit(unittester.exit_code);
|
||||
|
||||
} else {
|
||||
/* No - report successful command completion and continue program execution */
|
||||
unittester_log("[UT] Exit disabled, continuing execution\n");
|
||||
}
|
||||
unittester.cmd_id = UT_CMD_NOOP;
|
||||
unittester.status = UT_STATUS_IDLE;
|
||||
break;
|
||||
|
||||
case UT_CMD_CAPTURE_SCREEN_SNAPSHOT:
|
||||
/* Recompute screen */
|
||||
unittester.snap_img_width = 0;
|
||||
unittester.snap_img_height = 0;
|
||||
unittester.snap_img_xoffs = 0;
|
||||
unittester.snap_img_yoffs = 0;
|
||||
unittester.snap_overscan_width = 0;
|
||||
unittester.snap_overscan_height = 0;
|
||||
if (unittester.snap_monitor < 0x01 || (unittester.snap_monitor - 1) > MONITORS_NUM) {
|
||||
/* No monitor here - clear snapshot */
|
||||
unittester.snap_monitor = 0x00;
|
||||
} else if (video_get_type_monitor(unittester.snap_monitor - 1) == VIDEO_FLAG_TYPE_NONE) {
|
||||
/* Monitor disabled - clear snapshot */
|
||||
unittester.snap_monitor = 0x00;
|
||||
} else {
|
||||
/* Compute bounds for snapshot */
|
||||
const monitor_t *m = &monitors[unittester.snap_monitor - 1];
|
||||
unittester.snap_img_width = m->mon_xsize;
|
||||
unittester.snap_img_height = m->mon_ysize;
|
||||
unittester.snap_overscan_width = m->mon_xsize + m->mon_overscan_x;
|
||||
unittester.snap_overscan_height = m->mon_ysize + m->mon_overscan_y;
|
||||
unittester.snap_img_xoffs = (m->mon_overscan_x >> 1);
|
||||
unittester.snap_img_yoffs = (m->mon_overscan_y >> 1);
|
||||
/* Take snapshot */
|
||||
for (size_t y = 0; y < unittester.snap_overscan_height; y++) {
|
||||
for (size_t x = 0; x < unittester.snap_overscan_width; x++) {
|
||||
unittester_screen_buffer->line[y][x] = m->target_buffer->line[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* We have 12 bytes to read. */
|
||||
unittester_log("[UT] Screen snapshot - image %d x %d @ (%d, %d) in overscan %d x %d\n",
|
||||
unittester.snap_img_width,
|
||||
unittester.snap_img_height,
|
||||
unittester.snap_img_xoffs,
|
||||
unittester.snap_img_yoffs,
|
||||
unittester.snap_overscan_width,
|
||||
unittester.snap_overscan_height);
|
||||
unittester.status = UT_STATUS_AWAITING_READ;
|
||||
unittester.read_len = 12;
|
||||
break;
|
||||
|
||||
case UT_CMD_READ_SCREEN_SNAPSHOT_RECTANGLE:
|
||||
case UT_CMD_VERIFY_SCREEN_SNAPSHOT_RECTANGLE:
|
||||
/* Offset the X,Y offsets by the overscan offsets. */
|
||||
unittester.read_snap_xoffs += (int16_t) unittester.snap_img_xoffs;
|
||||
unittester.read_snap_yoffs += (int16_t) unittester.snap_img_yoffs;
|
||||
/* NOTE: Width * Height * 4 can potentially exceed a 32-bit number.
|
||||
So, we use 64-bit numbers instead.
|
||||
In practice, this will only happen if someone decides to request e.g. a 65535 x 65535 image,
|
||||
of which most of the pixels will be out of range anyway.
|
||||
*/
|
||||
unittester.read_len = ((uint64_t) unittester.read_snap_width) * ((uint64_t) unittester.read_snap_height) * 4;
|
||||
unittester.read_snap_crc = 0xFFFFFFFF;
|
||||
|
||||
if (unittester.cmd_id == UT_CMD_VERIFY_SCREEN_SNAPSHOT_RECTANGLE) {
|
||||
/* Read everything and compute CRC */
|
||||
uint32_t crc = 0xFFFFFFFF;
|
||||
for (uint64_t i = 0; i < unittester.read_len; i++) {
|
||||
crc ^= 0xFF & (uint32_t) unittester_read_snap_rect_idx(i);
|
||||
/* Use some bit twiddling until we have a table-based fast CRC-32 implementation */
|
||||
for (uint32_t j = 0; j < 8; j++) {
|
||||
crc = (crc >> 1) ^ ((-(crc & 0x1)) & 0xEDB88320);
|
||||
}
|
||||
}
|
||||
unittester.read_snap_crc = crc ^ 0xFFFFFFFF;
|
||||
|
||||
/* Set actual read length for CRC result */
|
||||
unittester.read_len = 4;
|
||||
unittester.status = UT_STATUS_AWAITING_READ;
|
||||
|
||||
} else {
|
||||
/* Do we have anything to read? */
|
||||
if (unittester.read_len >= 1) {
|
||||
/* Yes - start reads! */
|
||||
unittester.status = UT_STATUS_AWAITING_READ;
|
||||
} else {
|
||||
/* No - stop here. */
|
||||
unittester.cmd_id = UT_CMD_NOOP;
|
||||
unittester.status = UT_STATUS_IDLE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Nothing to write? Stop here. */
|
||||
unittester.cmd_id = UT_CMD_NOOP;
|
||||
unittester.status = UT_STATUS_IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/* Not handled here - possibly open bus! */
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
unittester_read(uint16_t port, UNUSED(void *priv))
|
||||
{
|
||||
uint8_t outval = 0xFF;
|
||||
|
||||
if (port == unittester.iobase_port + 0x00) {
|
||||
/* Status port */
|
||||
unittester_log("[UT] R -- Status = %02X\n", unittester.status);
|
||||
return unittester.status;
|
||||
} else if (port == unittester.iobase_port + 0x01) {
|
||||
/* Data port */
|
||||
/* unittester_log("[UT] R -- Data\n"); */
|
||||
|
||||
/* Skip if not awaiting */
|
||||
if ((unittester.status & UT_STATUS_AWAITING_READ) == 0)
|
||||
return 0xFF;
|
||||
|
||||
switch (unittester.cmd_id) {
|
||||
case UT_CMD_CAPTURE_SCREEN_SNAPSHOT:
|
||||
switch (unittester.read_offs) {
|
||||
case 0:
|
||||
outval = (uint8_t) (unittester.snap_img_width);
|
||||
break;
|
||||
case 1:
|
||||
outval = (uint8_t) (unittester.snap_img_width >> 8);
|
||||
break;
|
||||
case 2:
|
||||
outval = (uint8_t) (unittester.snap_img_height);
|
||||
break;
|
||||
case 3:
|
||||
outval = (uint8_t) (unittester.snap_img_height >> 8);
|
||||
break;
|
||||
case 4:
|
||||
outval = (uint8_t) (unittester.snap_overscan_width);
|
||||
break;
|
||||
case 5:
|
||||
outval = (uint8_t) (unittester.snap_overscan_width >> 8);
|
||||
break;
|
||||
case 6:
|
||||
outval = (uint8_t) (unittester.snap_overscan_height);
|
||||
break;
|
||||
case 7:
|
||||
outval = (uint8_t) (unittester.snap_overscan_height >> 8);
|
||||
break;
|
||||
case 8:
|
||||
outval = (uint8_t) (unittester.snap_img_xoffs);
|
||||
break;
|
||||
case 9:
|
||||
outval = (uint8_t) (unittester.snap_img_xoffs >> 8);
|
||||
break;
|
||||
case 10:
|
||||
outval = (uint8_t) (unittester.snap_img_yoffs);
|
||||
break;
|
||||
case 11:
|
||||
outval = (uint8_t) (unittester.snap_img_yoffs >> 8);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case UT_CMD_READ_SCREEN_SNAPSHOT_RECTANGLE:
|
||||
outval = unittester_read_snap_rect_idx(unittester.read_offs);
|
||||
break;
|
||||
|
||||
case UT_CMD_VERIFY_SCREEN_SNAPSHOT_RECTANGLE:
|
||||
outval = (uint8_t) (unittester.read_snap_crc >> (8 * unittester.read_offs));
|
||||
break;
|
||||
|
||||
/* This should not be reachable, but just in case... */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Advance read buffer */
|
||||
unittester.read_offs += 1;
|
||||
if (unittester.read_offs >= unittester.read_len) {
|
||||
/* Once fully read, we stop here. */
|
||||
unittester.cmd_id = UT_CMD_NOOP;
|
||||
unittester.status = UT_STATUS_IDLE;
|
||||
}
|
||||
|
||||
return outval;
|
||||
} else {
|
||||
/* Not handled here - possibly open bus! */
|
||||
return 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
unittester_trigger_write(UNUSED(uint16_t port), uint8_t val, UNUSED(void *priv))
|
||||
{
|
||||
/* This one gets quite spammy. */
|
||||
/* unittester_log("[UT] Trigger value %02X -> FSM1 = %02X, FSM2 = %02X, IOBASE = %04X\n", val, unittester.fsm1, unittester.fsm2, unittester.iobase_port); */
|
||||
|
||||
/* Update FSM2 */
|
||||
switch (unittester.fsm2) {
|
||||
/* IDLE: Do nothing - FSM1 will put us in the right state. */
|
||||
case UT_FSM2_IDLE:
|
||||
unittester.fsm2 = UT_FSM2_IDLE;
|
||||
break;
|
||||
|
||||
/* WAIT IOBASE 0: Set low byte of temporary IOBASE. */
|
||||
case UT_FSM2_WAIT_IOBASE_0:
|
||||
unittester.fsm2_new_iobase = ((uint16_t) val);
|
||||
unittester.fsm2 = UT_FSM2_WAIT_IOBASE_1;
|
||||
break;
|
||||
|
||||
/* WAIT IOBASE 0: Set high byte of temporary IOBASE and commit to the real IOBASE. */
|
||||
case UT_FSM2_WAIT_IOBASE_1:
|
||||
unittester.fsm2_new_iobase |= ((uint16_t) val) << 8;
|
||||
|
||||
unittester_log("[UT] Remapping IOBASE: %04X -> %04X\n", unittester.iobase_port, unittester.fsm2_new_iobase);
|
||||
|
||||
/* Unmap old IOBASE */
|
||||
if (unittester.iobase_port != 0xFFFF)
|
||||
io_removehandler(unittester.iobase_port, 2, unittester_read, NULL, NULL, unittester_write, NULL, NULL, NULL);
|
||||
unittester.iobase_port = 0xFFFF;
|
||||
|
||||
/* Map new IOBASE */
|
||||
unittester.iobase_port = unittester.fsm2_new_iobase;
|
||||
if (unittester.iobase_port != 0xFFFF)
|
||||
io_sethandler(unittester.iobase_port, 2, unittester_read, NULL, NULL, unittester_write, NULL, NULL, NULL);
|
||||
|
||||
/* Reset FSM2 to IDLE */
|
||||
unittester.fsm2 = UT_FSM2_IDLE;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Update FSM1 */
|
||||
switch (val) {
|
||||
case '8':
|
||||
unittester.fsm1 = UT_FSM1_WAIT_6;
|
||||
break;
|
||||
case '6':
|
||||
if (unittester.fsm1 == UT_FSM1_WAIT_6)
|
||||
unittester.fsm1 = UT_FSM1_WAIT_B;
|
||||
else
|
||||
unittester.fsm1 = UT_FSM1_WAIT_8;
|
||||
break;
|
||||
case 'B':
|
||||
if (unittester.fsm1 == UT_FSM1_WAIT_B)
|
||||
unittester.fsm1 = UT_FSM1_WAIT_o;
|
||||
else
|
||||
unittester.fsm1 = UT_FSM1_WAIT_8;
|
||||
break;
|
||||
case 'o':
|
||||
if (unittester.fsm1 == UT_FSM1_WAIT_o)
|
||||
unittester.fsm1 = UT_FSM1_WAIT_x;
|
||||
else
|
||||
unittester.fsm1 = UT_FSM1_WAIT_8;
|
||||
break;
|
||||
case 'x':
|
||||
if (unittester.fsm1 == UT_FSM1_WAIT_x) {
|
||||
unittester_log("[UT] Config activated, awaiting new IOBASE\n");
|
||||
unittester.fsm2 = UT_FSM2_WAIT_IOBASE_0;
|
||||
}
|
||||
unittester.fsm1 = UT_FSM1_WAIT_8;
|
||||
break;
|
||||
|
||||
default:
|
||||
unittester.fsm1 = UT_FSM1_WAIT_8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void *
|
||||
unittester_init(UNUSED(const device_t *info))
|
||||
{
|
||||
unittester = (struct unittester_state) unittester_defaults;
|
||||
|
||||
unittester_exit_enabled = !!device_get_config_int("exit_enabled");
|
||||
|
||||
if (unittester_screen_buffer == NULL)
|
||||
unittester_screen_buffer = create_bitmap(2048, 2048);
|
||||
|
||||
io_sethandler(unittester.trigger_port, 1, NULL, NULL, NULL, unittester_trigger_write, NULL, NULL, NULL);
|
||||
|
||||
unittester_log("[UT] 86Box Unit Tester initialised\n");
|
||||
|
||||
return &unittester; /* Dummy non-NULL value */
|
||||
}
|
||||
|
||||
static void
|
||||
unittester_close(UNUSED(void *priv))
|
||||
{
|
||||
io_removehandler(unittester.trigger_port, 1, NULL, NULL, NULL, unittester_trigger_write, NULL, NULL, NULL);
|
||||
|
||||
if (unittester.iobase_port != 0xFFFF)
|
||||
io_removehandler(unittester.iobase_port, 2, unittester_read, NULL, NULL, unittester_write, NULL, NULL, NULL);
|
||||
unittester.iobase_port = 0xFFFF;
|
||||
|
||||
if (unittester_screen_buffer != NULL) {
|
||||
destroy_bitmap(unittester_screen_buffer);
|
||||
unittester_screen_buffer = NULL;
|
||||
}
|
||||
|
||||
unittester_log("[UT] 86Box Unit Tester closed\n");
|
||||
}
|
||||
|
||||
const device_t unittester_device = {
|
||||
.name = "86Box Unit Tester",
|
||||
.internal_name = "unittester",
|
||||
.flags = DEVICE_ISA,
|
||||
.local = 0,
|
||||
.init = unittester_init,
|
||||
.close = unittester_close,
|
||||
.reset = NULL,
|
||||
{ .available = NULL },
|
||||
.speed_changed = NULL,
|
||||
.force_redraw = NULL,
|
||||
.config = unittester_config,
|
||||
};
|
@@ -125,6 +125,7 @@ extern int gfxcard[2]; /* (C) graphics/video card */
|
||||
extern char video_shader[512]; /* (C) video */
|
||||
extern int bugger_enabled; /* (C) enable ISAbugger */
|
||||
extern int postcard_enabled; /* (C) enable POST card */
|
||||
extern int unittester_enabled; /* (C) enable unit tester device */
|
||||
extern int isamem_type[]; /* (C) enable ISA mem cards */
|
||||
extern int isartc_type; /* (C) enable ISA RTC card */
|
||||
extern int sound_is_float; /* (C) sound uses FP values */
|
||||
|
37
src/include/86box/unittester.h
Normal file
37
src/include/86box/unittester.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Debug device for assisting in unit testing.
|
||||
* See doc/specifications/86box-unit-tester.md for more info.
|
||||
* If modifying the protocol, you MUST modify the specification
|
||||
* and increment the version number.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: GreaseMonkey, <thematrixeatsyou+86b@gmail.com>
|
||||
*
|
||||
* Copyright 2024 GreaseMonkey.
|
||||
*/
|
||||
|
||||
#ifndef UNITTESTER_H
|
||||
#define UNITTESTER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Global variables. */
|
||||
extern const device_t unittester_device;
|
||||
|
||||
/* Functions. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /*UNITTESTER_H*/
|
@@ -23,6 +23,7 @@ extern "C" {
|
||||
#include <86box/machine.h>
|
||||
#include <86box/isamem.h>
|
||||
#include <86box/isartc.h>
|
||||
#include <86box/unittester.h>
|
||||
}
|
||||
|
||||
#include "qt_deviceconfig.hpp"
|
||||
@@ -44,7 +45,9 @@ SettingsOtherPeripherals::onCurrentMachineChanged(int machineId)
|
||||
bool machineHasIsa = (machine_has_bus(machineId, MACHINE_BUS_ISA) > 0);
|
||||
ui->checkBoxISABugger->setChecked((machineHasIsa && (bugger_enabled > 0)) ? true : false);
|
||||
ui->checkBoxPOSTCard->setChecked(postcard_enabled > 0 ? true : false);
|
||||
ui->checkBoxUnitTester->setChecked(unittester_enabled > 0 ? true : false);
|
||||
ui->checkBoxISABugger->setEnabled(machineHasIsa);
|
||||
ui->pushButtonConfigureUT->setEnabled(unittester_enabled > 0);
|
||||
ui->comboBoxRTC->setEnabled(machineHasIsa);
|
||||
ui->pushButtonConfigureRTC->setEnabled(machineHasIsa);
|
||||
|
||||
@@ -112,9 +115,10 @@ void
|
||||
SettingsOtherPeripherals::save()
|
||||
{
|
||||
/* Other peripherals category */
|
||||
bugger_enabled = ui->checkBoxISABugger->isChecked() ? 1 : 0;
|
||||
postcard_enabled = ui->checkBoxPOSTCard->isChecked() ? 1 : 0;
|
||||
isartc_type = ui->comboBoxRTC->currentData().toInt();
|
||||
bugger_enabled = ui->checkBoxISABugger->isChecked() ? 1 : 0;
|
||||
postcard_enabled = ui->checkBoxPOSTCard->isChecked() ? 1 : 0;
|
||||
unittester_enabled = ui->checkBoxUnitTester->isChecked() ? 1 : 0;
|
||||
isartc_type = ui->comboBoxRTC->currentData().toInt();
|
||||
|
||||
/* ISA memory boards. */
|
||||
for (int i = 0; i < ISAMEM_MAX; i++) {
|
||||
@@ -197,3 +201,15 @@ SettingsOtherPeripherals::on_pushButtonConfigureCard4_clicked()
|
||||
{
|
||||
DeviceConfig::ConfigureDevice(isamem_get_device(ui->comboBoxCard4->currentData().toInt()), 4, qobject_cast<Settings *>(Settings::settings));
|
||||
}
|
||||
|
||||
void
|
||||
SettingsOtherPeripherals::on_checkBoxUnitTester_stateChanged(int arg1)
|
||||
{
|
||||
ui->pushButtonConfigureUT->setEnabled(arg1 != 0);
|
||||
}
|
||||
|
||||
void
|
||||
SettingsOtherPeripherals::on_pushButtonConfigureUT_clicked()
|
||||
{
|
||||
DeviceConfig::ConfigureDevice(&unittester_device);
|
||||
}
|
||||
|
@@ -30,6 +30,8 @@ private slots:
|
||||
void on_comboBoxCard1_currentIndexChanged(int index);
|
||||
void on_pushButtonConfigureRTC_clicked();
|
||||
void on_comboBoxRTC_currentIndexChanged(int index);
|
||||
void on_checkBoxUnitTester_stateChanged(int arg1);
|
||||
void on_pushButtonConfigureUT_clicked();
|
||||
|
||||
private:
|
||||
Ui::SettingsOtherPeripherals *ui;
|
||||
|
@@ -192,6 +192,30 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxUnitTester">
|
||||
<property name="text">
|
||||
<string>86Box Unit Tester</string>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonConfigureUT">
|
||||
<property name="text">
|
||||
<string>Configure</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
Reference in New Issue
Block a user