diff --git a/defects/hatari-0001/patch/hatari-0001.patch b/defects/hatari-0001/patch/hatari-0001.patch new file mode 100644 index 000000000..afc486c87 --- /dev/null +++ b/defects/hatari-0001/patch/hatari-0001.patch @@ -0,0 +1,101 @@ +# UNDF: UNDF-2026-000001040 +--- a/src/ikbd.c ++++ b/src/ikbd.c +@@ -218,6 +218,28 @@ static const struct { + void (*pCallFunction)(void); + } KeyboardCommands[] = + { ++ /* Dispatch table built at init from KeyboardCommands[] for O(1) lookup. */ ++ /* See IKBD_InitDispatch() and IKBD_RunKeyboardCommand(). */ + /* Known messages, counts include command byte */ + { 0x80,2, IKBD_Cmd_Reset }, + { 0x07,2, IKBD_Cmd_MouseAction }, +@@ -258,6 +280,33 @@ static const struct { + { 0xFF,0, NULL } /* Term */ + }; + ++/* Dispatch table: indexed by command byte, holds index into KeyboardCommands[] ++ * or -1 for unknown commands. Built once by IKBD_InitDispatch(). */ ++static int IKBDCmdIndex[256]; ++ ++/** ++ * Build the O(1) command-dispatch index from KeyboardCommands[]. ++ * Called once during IKBD initialisation. ++ */ ++static void IKBD_InitDispatch(void) ++{ ++ int i; ++ for (i = 0; i < 256; i++) ++ IKBDCmdIndex[i] = -1; ++ for (i = 0; KeyboardCommands[i].Command != 0xFF; i++) ++ IKBDCmdIndex[KeyboardCommands[i].Command] = i; ++} ++ + + /*----------------------------------------------------------------------*/ + /* Variables/defines/functions used to transfer data between the */ +@@ -830,6 +879,7 @@ void IKBD_Reset(bool bCold) + /* Reset */ + memset(&Keyboard, 0, sizeof(Keyboard)); + ++ IKBD_InitDispatch(); ++ + /* IKBD gets reset whenever the computer does */ + IKBD_ResetKeyboardProcessor(); + +@@ -1854,22 +1904,22 @@ static void IKBD_RunKeyboardCommand(uint8_t aciabyte) + { +- int i=0; + + /* Write into our keyboard input buffer if it's not full yet */ + if ( Keyboard.nBytesInInputBuffer < SIZE_KEYBOARDINPUT_BUFFER ) + Keyboard.InputBuffer[Keyboard.nBytesInInputBuffer++] = aciabyte; + +- /* Now check bytes to see if we have a valid/in-valid command string set */ +- while (KeyboardCommands[i].Command!=0xff) +- { +- /* Found command? */ +- if (KeyboardCommands[i].Command==Keyboard.InputBuffer[0]) +- { +- /* If the command is complete (with its possible parameters) we can execute it */ +- /* Else, we wait for the next bytes until the command is complete */ +- if (KeyboardCommands[i].NumParameters==Keyboard.nBytesInInputBuffer) +- { +- /* Any new valid command will unpause the output (if command 0x13 was used) */ +- Keyboard.PauseOutput = false; +- +- CALL_VAR(KeyboardCommands[i].pCallFunction); +- Keyboard.nBytesInInputBuffer = 0; /* Clear input buffer after processing a command */ +- } +- +- return; +- } +- +- i++; +- } +- +- /* Command not known, reset buffer(IKBD assumes a NOP) */ +- Keyboard.nBytesInInputBuffer = 0; ++ /* O(1) dispatch: look up command byte directly, no linear scan needed. */ ++ { ++ int idx = IKBDCmdIndex[(unsigned char)Keyboard.InputBuffer[0]]; ++ if (idx >= 0) ++ { ++ /* If the command is complete (with its possible parameters) we can execute it */ ++ /* Else, we wait for the next bytes until the command is complete */ ++ if (KeyboardCommands[idx].NumParameters==Keyboard.nBytesInInputBuffer) ++ { ++ /* Any new valid command will unpause the output (if command 0x13 was used) */ ++ Keyboard.PauseOutput = false; ++ ++ CALL_VAR(KeyboardCommands[idx].pCallFunction); ++ Keyboard.nBytesInInputBuffer = 0; /* Clear input buffer after processing a command */ ++ } ++ ++ return; ++ } ++ ++ /* Command not known, reset buffer (IKBD assumes a NOP) */ ++ Keyboard.nBytesInInputBuffer = 0; ++ } + } diff --git a/defects/hatari-0001/test/test_hatari_0001.c b/defects/hatari-0001/test/test_hatari_0001.c new file mode 100644 index 000000000..f4d144d88 --- /dev/null +++ b/defects/hatari-0001/test/test_hatari_0001.c @@ -0,0 +1,199 @@ +/* + * test_hatari_0001.c + * + * Unit test for hatari-0001: IKBD_RunKeyboardCommand O(C) linear scan + * replaced with O(1) dispatch table. + * + * Tests that the dispatch table correctly maps all known command bytes + * and returns -1 for unknown bytes, verifying O(1) correctness. + */ + +#include +#include +#include +#include + +/* Minimal reproduction of the KeyboardCommands table and dispatch logic. */ + +typedef struct { + uint8_t Command; + uint8_t NumParameters; + void (*pCallFunction)(void); +} KeyboardCommand; + +/* Representative subset of IKBD commands from Hatari's ikbd.c */ +static const KeyboardCommand KeyboardCommands[] = { + { 0x80, 2, NULL }, /* Reset */ + { 0x07, 2, NULL }, /* MouseAction */ + { 0x08, 1, NULL }, /* RelMouseMode */ + { 0x09, 5, NULL }, /* AbsMouseMode */ + { 0x0A, 3, NULL }, /* MouseCursorKeycodes */ + { 0x0B, 3, NULL }, /* SetMouseThreshold */ + { 0x0C, 3, NULL }, /* SetMouseScale */ + { 0x0D, 1, NULL }, /* ReadAbsMousePos */ + { 0x0E, 6, NULL }, /* SetInternalMousePos */ + { 0x0F, 1, NULL }, /* SetYAxisDown */ + { 0x10, 1, NULL }, /* SetYAxisUp */ + { 0x11, 1, NULL }, /* StartKeyboardTransfer */ + { 0x12, 1, NULL }, /* TurnMouseOff */ + { 0x13, 1, NULL }, /* StopKeyboardTransfer */ + { 0x14, 1, NULL }, /* ReturnJoystickAuto */ + { 0x15, 1, NULL }, /* StopJoystick */ + { 0x16, 1, NULL }, /* ReturnJoystick */ + { 0x17, 2, NULL }, /* SetJoystickMonitoring */ + { 0x18, 1, NULL }, /* SetJoystickFireDuration */ + { 0x19, 7, NULL }, /* SetCursorForJoystick */ + { 0x1A, 1, NULL }, /* DisableJoysticks */ + { 0x1B, 7, NULL }, /* SetClock */ + { 0x1C, 1, NULL }, /* ReadClock */ + { 0x20, 4, NULL }, /* LoadMemory */ + { 0x21, 3, NULL }, /* ReadMemory */ + { 0x22, 3, NULL }, /* Execute */ + /* Report messages */ + { 0x87, 1, NULL }, /* ReportMouseAction */ + { 0x88, 1, NULL }, /* ReportMouseMode */ + { 0x89, 1, NULL }, /* ReportMouseMode */ + { 0x8A, 1, NULL }, /* ReportMouseMode */ + { 0x8B, 1, NULL }, /* ReportMouseThreshold */ + { 0x8C, 1, NULL }, /* ReportMouseScale */ + { 0x8F, 1, NULL }, /* ReportMouseVertical */ + { 0x90, 1, NULL }, /* ReportMouseVertical */ + { 0x92, 1, NULL }, /* ReportMouseAvailability */ + { 0x94, 1, NULL }, /* ReportJoystickMode */ + { 0x95, 1, NULL }, /* ReportJoystickMode */ + { 0x99, 1, NULL }, /* ReportJoystickMode */ + { 0x9A, 1, NULL }, /* ReportJoystickAvailability */ + { 0xFF, 0, NULL } /* Terminator */ +}; + +/* O(C) original: linear scan */ +static int ikbd_find_command_linear(uint8_t cmd) +{ + int i = 0; + while (KeyboardCommands[i].Command != 0xFF) { + if (KeyboardCommands[i].Command == cmd) + return i; + i++; + } + return -1; +} + +/* O(1) patched: dispatch table */ +static int IKBDCmdIndex[256]; + +static void ikbd_init_dispatch(void) +{ + int i; + for (i = 0; i < 256; i++) + IKBDCmdIndex[i] = -1; + for (i = 0; KeyboardCommands[i].Command != 0xFF; i++) + IKBDCmdIndex[KeyboardCommands[i].Command] = i; +} + +static int ikbd_find_command_dispatch(uint8_t cmd) +{ + return IKBDCmdIndex[cmd]; +} + +/* Count iterations in linear scan for given command byte */ +static int ikbd_linear_iterations(uint8_t cmd) +{ + int i = 0, count = 0; + while (KeyboardCommands[i].Command != 0xFF) { + count++; + if (KeyboardCommands[i].Command == cmd) + return count; + i++; + } + return count; /* not found, scanned all */ +} + +int main(void) +{ + int i, linear_idx, dispatch_idx; + int total_tests = 0, pass = 0; + int max_linear_iter = 0, total_linear_iter = 0; + + ikbd_init_dispatch(); + + /* Test 1: dispatch table matches linear scan for all 256 byte values */ + printf("Test 1: dispatch table correctness for all 256 command bytes\n"); + for (i = 0; i < 256; i++) { + linear_idx = ikbd_find_command_linear((uint8_t)i); + dispatch_idx = ikbd_find_command_dispatch((uint8_t)i); + total_tests++; + if (linear_idx == dispatch_idx) { + pass++; + } else { + printf(" FAIL byte=0x%02x linear=%d dispatch=%d\n", i, linear_idx, dispatch_idx); + } + } + printf(" %d/%d passed\n", pass, total_tests); + assert(pass == total_tests); + + /* Test 2: O(1) vs O(C) -- dispatch always 1 step, linear varies */ + printf("Test 2: iteration count reduction\n"); + { + /* Known commands: reset (first entry scanned), 0x9A (last entry scanned) */ + int iter_first = ikbd_linear_iterations(0x80); /* 0x80 is first */ + int iter_last = ikbd_linear_iterations(0x9A); /* 0x9A is near last */ + int iter_unknown = ikbd_linear_iterations(0x01); /* unknown -- scans all */ + + /* Count total commands (excluding terminator) */ + int num_cmds = 0; + while (KeyboardCommands[num_cmds].Command != 0xFF) num_cmds++; + + printf(" first-cmd iterations: %d (linear) vs 1 (dispatch)\n", iter_first); + printf(" last-cmd iterations: %d (linear) vs 1 (dispatch)\n", iter_last); + printf(" unknown-cmd iterations: %d (linear) vs 1 (dispatch)\n", iter_unknown); + printf(" total commands in table: %d\n", num_cmds); + + assert(iter_first >= 1); + assert(iter_last > iter_first); /* later entries cost more in linear */ + assert(iter_unknown >= num_cmds); /* unknown scans everything */ + + /* dispatch is O(1): always 1 table lookup regardless */ + total_linear_iter += iter_first + iter_last + iter_unknown; + max_linear_iter = iter_unknown; + + printf(" PASS: dispatch eliminates up to %dx overhead vs worst-case linear\n", + max_linear_iter); + } + + /* Test 3: NumParameters preserved correctly */ + printf("Test 3: NumParameters preserved in dispatch table\n"); + { + int idx; + /* 0x09 AbsMouseMode expects 5 parameters */ + idx = ikbd_find_command_dispatch(0x09); + assert(idx >= 0); + assert(KeyboardCommands[idx].NumParameters == 5); + + /* 0x19 SetCursorForJoystick expects 7 parameters */ + idx = ikbd_find_command_dispatch(0x19); + assert(idx >= 0); + assert(KeyboardCommands[idx].NumParameters == 7); + + /* 0x08 RelMouseMode expects 1 parameter */ + idx = ikbd_find_command_dispatch(0x08); + assert(idx >= 0); + assert(KeyboardCommands[idx].NumParameters == 1); + + printf(" PASS: NumParameters correct for 0x09 (5), 0x19 (7), 0x08 (1)\n"); + } + + /* Test 4: unknown commands return -1 */ + printf("Test 4: unknown command bytes return -1\n"); + { + /* 0x01, 0x02, 0x03 are not in the IKBD command set */ + assert(ikbd_find_command_dispatch(0x01) == -1); + assert(ikbd_find_command_dispatch(0x02) == -1); + assert(ikbd_find_command_dispatch(0x03) == -1); + assert(ikbd_find_command_dispatch(0x00) == -1); + printf(" PASS: 0x00, 0x01, 0x02, 0x03 all return -1\n"); + } + + printf("\nAll tests passed. O(1) dispatch table eliminates O(%d) linear scan.\n", + max_linear_iter); + return 0; +}