Wed, 16 Apr 2014 02:20:43 -0600
fixed bus error handling for real this time (save registers before every instruction and push the saved registers if a bus error occurs, since the instruction may have changed registers before the bus error, and also stop the instruction immediately with longjmp so it won't change memory after the bus error)
This isn't actually what a real 68k does, but it is a good enough approximation. A real 68k will jump back into the middle of the faulted instruction and resume it from the memory access that faulted as opposed to restarting from the beginning like this CPU emulation does. It would be a lot harder to do that with the way this CPU library is designed. Newer versions of MESS basically do the same thing (they use a newer version of this library).
philpem@80 | 1 | #include <stdbool.h> |
philpem@74 | 2 | #include "SDL.h" |
philpem@99 | 3 | #include "utils.h" |
philpem@86 | 4 | #include "keyboard.h" |
philpem@74 | 5 | |
philpem@99 | 6 | // Enable/disable KBC debugging |
philpem@99 | 7 | #define kbc_debug false |
philpem@99 | 8 | |
philpem@74 | 9 | /** |
philpem@74 | 10 | * Key map -- a mapping from SDLK_xxx constants to scancodes and vice versa. |
philpem@74 | 11 | */ |
philpem@74 | 12 | struct { |
philpem@74 | 13 | SDLKey key; ///< SDLK_xxx key code constant |
philpem@74 | 14 | int extended; ///< 1 if this is an extended keycode |
philpem@74 | 15 | unsigned char scancode; ///< Keyboard scan code |
philpem@74 | 16 | } keymap[] = { |
philpem@74 | 17 | { SDLK_UP, 0, 0x01 }, // ROLL/Up [UpArrow] |
philpem@74 | 18 | { SDLK_KP2, 0, 0x01 }, // ROLL/Up [Keypad 2] |
philpem@74 | 19 | // { SDLK_, 1, 0x02 }, // Clear Line |
philpem@74 | 20 | // { SDLK_, 1, 0x03 }, // Rstrt / Ref |
andrew@146 | 21 | { SDLK_ESCAPE, 1, 0x04 }, // Exit |
philpem@74 | 22 | { SDLK_KP1, 0, 0x05 }, // PREV [Keypad 1] |
philpem@74 | 23 | // { SDLK_, 1, 0x06 }, // Msg |
andrew@146 | 24 | { SDLK_BACKSPACE, 1, 0x07 }, // Cancl |
philpem@74 | 25 | { SDLK_BACKSPACE, 0, 0x08 }, // Backspace |
philpem@74 | 26 | { SDLK_TAB, 0, 0x09 }, // Tab |
andrew@146 | 27 | { SDLK_RETURN, 1, 0x0a }, // ENTER |
philpem@74 | 28 | { SDLK_DOWN, 0, 0x0b }, // ROLL/Down [DownArrow] |
philpem@74 | 29 | { SDLK_KP0, 0, 0x0b }, // ROLL/Down [Keypad 0] |
philpem@74 | 30 | { SDLK_KP3, 0, 0x0c }, // NEXT [Keypad 3] |
philpem@74 | 31 | { SDLK_RETURN, 0, 0x0d }, // RETURN [Return] |
philpem@74 | 32 | { SDLK_LEFT, 0, 0x0e }, // <-- [LeftArrow] |
philpem@74 | 33 | { SDLK_KP_MINUS, 0, 0x0e }, // <-- [Keypad -] |
philpem@74 | 34 | { SDLK_RIGHT, 0, 0x0f }, // --> [RightArrow] |
philpem@74 | 35 | { SDLK_KP_PERIOD, 0, 0x0f }, // --> [Keypad .] |
philpem@74 | 36 | // { SDLK_, 1, 0x10 }, // Creat |
philpem@74 | 37 | // { SDLK_, 1, 0x11 }, // Save |
philpem@74 | 38 | // { SDLK_, 1, 0x12 }, // Move |
philpem@74 | 39 | // { SDLK_, 1, 0x13 }, // Ops |
philpem@74 | 40 | // { SDLK_, 1, 0x14 }, // Copy |
philpem@74 | 41 | { SDLK_F1, 0, 0x15 }, // F1 |
philpem@74 | 42 | { SDLK_F2, 0, 0x16 }, // F2 |
philpem@74 | 43 | { SDLK_F3, 0, 0x17 }, // F3 |
philpem@74 | 44 | { SDLK_F4, 0, 0x18 }, // F4 |
philpem@74 | 45 | { SDLK_F5, 0, 0x19 }, // F5 |
philpem@74 | 46 | { SDLK_F6, 0, 0x1a }, // F6 |
philpem@74 | 47 | { SDLK_ESCAPE, 0, 0x1b }, // ESC/DEL [Escape] |
philpem@74 | 48 | { SDLK_F7, 0, 0x1c }, // F7 |
philpem@74 | 49 | { SDLK_F8, 0, 0x1d }, // F8 |
philpem@74 | 50 | // { SDLK_, 1, 0x1e }, // Suspd |
philpem@74 | 51 | // { SDLK_, 1, 0x1f }, // Rsume |
philpem@74 | 52 | { SDLK_SPACE, 0, 0x20 }, // SPACE [Spacebar] |
philpem@74 | 53 | // { SDLK_, 1, 0x21 }, // Undo |
philpem@74 | 54 | // { SDLK_, 1, 0x22 }, // Redo |
philpem@74 | 55 | // { SDLK_, 1, 0x23 }, // FIND |
philpem@74 | 56 | // { SDLK_, 1, 0x24 }, // RPLAC |
philpem@74 | 57 | { SDLK_BREAK, 0, 0x25 }, // RESET/BREAK [Pause/Break] |
philpem@74 | 58 | // { SDLK_, 1, 0x26 }, // DleteChar |
philpem@74 | 59 | { SDLK_QUOTE, 0, 0x27 }, // ' (single-quote) |
philpem@74 | 60 | // { SDLK_, 1, 0x28 }, // SLCT/MARK |
philpem@74 | 61 | // { SDLK_, 1, 0x29 }, // INPUT/MODE |
philpem@74 | 62 | // { SDLK_, 1, 0x2a }, // HELP |
philpem@74 | 63 | // Keycode 2B not used |
philpem@74 | 64 | { SDLK_COMMA, 0, 0x2c }, // , [Comma] |
philpem@74 | 65 | { SDLK_MINUS, 0, 0x2d }, // - [Dash] |
philpem@74 | 66 | { SDLK_PERIOD, 0, 0x2e }, // . [Period] |
philpem@74 | 67 | { SDLK_SLASH, 0, 0x2f }, // / [Forward-slash] |
philpem@74 | 68 | { SDLK_0, 0, 0x30 }, // 0 |
philpem@74 | 69 | { SDLK_1, 0, 0x31 }, // 1 |
philpem@74 | 70 | { SDLK_2, 0, 0x32 }, // 2 |
philpem@74 | 71 | { SDLK_3, 0, 0x33 }, // 3 |
philpem@74 | 72 | { SDLK_4, 0, 0x34 }, // 4 |
philpem@74 | 73 | { SDLK_5, 0, 0x35 }, // 5 |
philpem@74 | 74 | { SDLK_6, 0, 0x36 }, // 6 |
philpem@74 | 75 | { SDLK_7, 0, 0x37 }, // 7 |
philpem@74 | 76 | { SDLK_8, 0, 0x38 }, // 8 |
philpem@74 | 77 | { SDLK_9, 0, 0x39 }, // 9 |
philpem@74 | 78 | // Keycode 3A not used |
philpem@74 | 79 | { SDLK_SEMICOLON, 0, 0x3b }, // ; [Semicolon] |
philpem@74 | 80 | // Keycode 3C not used |
philpem@74 | 81 | { SDLK_EQUALS, 0, 0x3d }, // = [Equals] |
philpem@74 | 82 | // Keycodes 3E, 3F, 40 not used |
philpem@74 | 83 | // { SDLK_, 1, 0x41 }, // CMD |
philpem@74 | 84 | // { SDLK_, 1, 0x42 }, // CLOSE/OPEN |
philpem@74 | 85 | { SDLK_KP7, 0, 0x43 }, // PRINT |
philpem@74 | 86 | { SDLK_KP8, 0, 0x44 }, // CLEAR/RFRSH |
philpem@74 | 87 | { SDLK_CAPSLOCK, 0, 0x45 }, // Caps Lock |
philpem@74 | 88 | { SDLK_KP9, 0, 0x46 }, // PAGE |
philpem@74 | 89 | { SDLK_KP4, 0, 0x47 }, // BEG |
philpem@74 | 90 | { SDLK_LSHIFT, 0, 0x48 }, // Left Shift |
philpem@74 | 91 | { SDLK_RSHIFT, 0, 0x49 }, // Right Shift |
philpem@74 | 92 | { SDLK_HOME, 0, 0x4a }, // Home |
philpem@74 | 93 | { SDLK_KP5, 0, 0x4a }, // Home [Keypad 5] |
philpem@74 | 94 | { SDLK_END, 0, 0x4b }, // End |
philpem@74 | 95 | { SDLK_KP6, 0, 0x4b }, // End [Keypad 6] |
philpem@74 | 96 | { SDLK_LCTRL, 0, 0x4c }, // Left Ctrl? \___ not sure which is left and which is right... |
philpem@74 | 97 | { SDLK_RCTRL, 0, 0x4d }, // Right Ctrl? / |
philpem@74 | 98 | // Keycodes 4E thru 5A not used |
philpem@74 | 99 | { SDLK_LEFTBRACKET, 0, 0x5b }, // [ |
philpem@74 | 100 | { SDLK_BACKSLASH, 0, 0x5c }, // \ (backslash) |
philpem@74 | 101 | { SDLK_RIGHTBRACKET, 0, 0x5d }, // ] |
philpem@74 | 102 | // Keycodes 5E, 5F not used |
philpem@74 | 103 | { SDLK_BACKQUOTE, 0, 0x60 }, // ` |
philpem@74 | 104 | { SDLK_a, 0, 0x61 }, // A |
philpem@74 | 105 | { SDLK_b, 0, 0x62 }, // B |
philpem@74 | 106 | { SDLK_c, 0, 0x63 }, // C |
philpem@74 | 107 | { SDLK_d, 0, 0x64 }, // D |
philpem@74 | 108 | { SDLK_e, 0, 0x65 }, // E |
philpem@74 | 109 | { SDLK_f, 0, 0x66 }, // F |
philpem@74 | 110 | { SDLK_g, 0, 0x67 }, // G |
philpem@74 | 111 | { SDLK_h, 0, 0x68 }, // H |
philpem@74 | 112 | { SDLK_i, 0, 0x69 }, // I |
philpem@74 | 113 | { SDLK_j, 0, 0x6a }, // J |
philpem@74 | 114 | { SDLK_k, 0, 0x6b }, // K |
philpem@74 | 115 | { SDLK_l, 0, 0x6c }, // L |
philpem@74 | 116 | { SDLK_m, 0, 0x6d }, // M |
philpem@74 | 117 | { SDLK_n, 0, 0x6e }, // N |
philpem@74 | 118 | { SDLK_o, 0, 0x6f }, // O |
philpem@74 | 119 | { SDLK_p, 0, 0x70 }, // P |
philpem@74 | 120 | { SDLK_q, 0, 0x71 }, // Q |
philpem@74 | 121 | { SDLK_r, 0, 0x72 }, // R |
philpem@74 | 122 | { SDLK_s, 0, 0x73 }, // S |
philpem@74 | 123 | { SDLK_t, 0, 0x74 }, // T |
philpem@74 | 124 | { SDLK_u, 0, 0x75 }, // U |
philpem@74 | 125 | { SDLK_v, 0, 0x76 }, // V |
philpem@74 | 126 | { SDLK_w, 0, 0x77 }, // W |
philpem@74 | 127 | { SDLK_x, 0, 0x78 }, // X |
philpem@74 | 128 | { SDLK_y, 0, 0x79 }, // Y |
philpem@74 | 129 | { SDLK_z, 0, 0x7a }, // Z |
philpem@74 | 130 | // Keycodes 7B, 7C, 7D not used |
philpem@83 | 131 | { SDLK_NUMLOCK, 0, 0x7e }, // Numlock |
philpem@83 | 132 | { SDLK_DELETE, 0, 0x7f } // Dlete |
philpem@74 | 133 | }; |
philpem@74 | 134 | |
philpem@74 | 135 | /** |
philpem@86 | 136 | * List of special key codes |
philpem@86 | 137 | */ |
philpem@86 | 138 | enum { |
philpem@86 | 139 | KEY_ALL_UP = 0x40, ///< All keys up |
philpem@86 | 140 | KEY_LIST_END = 0x80, ///< End of key code list |
philpem@86 | 141 | KEY_BEGIN_MOUSE = 0xCF, ///< Mouse data follows |
philpem@86 | 142 | KEY_BEGIN_KEYBOARD = 0xDF, ///< Keyboard data follows |
philpem@86 | 143 | }; |
philpem@86 | 144 | |
philpem@86 | 145 | /** |
philpem@86 | 146 | * List of keyboard commands |
philpem@74 | 147 | */ |
philpem@86 | 148 | enum { |
philpem@86 | 149 | KEY_CMD_RESET = 0x92, ///< Reset keyboard |
philpem@86 | 150 | KEY_CMD_CAPSLED_OFF = 0xB1, ///< Caps Lock LED off--CHECK! |
philpem@86 | 151 | KEY_CMD_CAPSLED_ON = 0xB0, ///< Caps Lock LED on --CHECK! |
philpem@86 | 152 | KEY_CMD_NUMLED_OFF = 0xA1, ///< Num Lock LED off --CHECK! |
philpem@86 | 153 | KEY_CMD_NUMLED_ON = 0xA0, ///< Num Lock LED on --CHECK! |
philpem@86 | 154 | KEY_CMD_MOUSE_ENABLE = 0xD0, ///< Enable mouse |
philpem@86 | 155 | KEY_CMD_MOUSE_DISABLE = 0xD1 ///< Disable mouse |
philpem@86 | 156 | }; |
philpem@74 | 157 | |
philpem@80 | 158 | void keyboard_init(KEYBOARD_STATE *ks) |
philpem@74 | 159 | { |
philpem@74 | 160 | // Set all key states to "not pressed" |
philpem@80 | 161 | for (int i=0; i<(sizeof(ks->keystate)/sizeof(ks->keystate[0])); i++) { |
philpem@80 | 162 | ks->keystate[i] = 0; |
philpem@74 | 163 | } |
philpem@80 | 164 | |
philpem@80 | 165 | // Reset the R/W pointers and length |
philpem@80 | 166 | ks->readp = ks->writep = ks->buflen = 0; |
philpem@91 | 167 | |
philpem@91 | 168 | // Clear the update flag |
philpem@91 | 169 | ks->update_flag = false; |
philpem@74 | 170 | } |
philpem@74 | 171 | |
philpem@80 | 172 | void keyboard_event(KEYBOARD_STATE *ks, SDL_Event *ev) |
philpem@74 | 173 | { |
philpem@90 | 174 | int v = 0; |
philpem@90 | 175 | switch (ev->type) { |
philpem@90 | 176 | case SDL_KEYDOWN: |
philpem@90 | 177 | // Key down (pressed) |
philpem@90 | 178 | v = 1; |
philpem@90 | 179 | break; |
philpem@90 | 180 | case SDL_KEYUP: |
philpem@90 | 181 | // Key up (released) |
philpem@90 | 182 | v = 0; |
philpem@90 | 183 | break; |
philpem@90 | 184 | default: |
philpem@90 | 185 | // Not a keyboard event |
philpem@90 | 186 | return; |
philpem@86 | 187 | } |
philpem@86 | 188 | |
philpem@82 | 189 | // scan the keymap |
philpem@90 | 190 | for (int i=0; i < sizeof(keymap)/sizeof(keymap[0]); i++) { |
philpem@90 | 191 | if (keymap[i].key == ev->key.keysym.sym) { |
philpem@90 | 192 | // Keycode match. Is this an Extended Map key? |
philpem@86 | 193 | if (keymap[i].extended) { |
philpem@90 | 194 | // Yes -- need ALT set when pressing the key for this to be a match |
philpem@86 | 195 | if (ev->key.keysym.mod & KMOD_ALT) { |
philpem@90 | 196 | ks->keystate[keymap[i].scancode] = v; |
philpem@94 | 197 | ks->update_flag = true; |
philpem@86 | 198 | break; |
philpem@86 | 199 | } |
philpem@86 | 200 | } else { |
philpem@90 | 201 | // Standard Map key. ALT must NOT be pressed for this to be a match |
philpem@86 | 202 | if (!(ev->key.keysym.mod & KMOD_ALT)) { |
philpem@90 | 203 | ks->keystate[keymap[i].scancode] = v; |
philpem@94 | 204 | ks->update_flag = true; |
philpem@86 | 205 | break; |
philpem@86 | 206 | } |
philpem@86 | 207 | } |
philpem@86 | 208 | } |
philpem@86 | 209 | } |
philpem@74 | 210 | } |
philpem@80 | 211 | void keyboard_scan(KEYBOARD_STATE *ks) |
philpem@80 | 212 | { |
philpem@84 | 213 | int nkeys = 0; |
philpem@84 | 214 | |
philpem@91 | 215 | // Skip doing the scan if the keyboard hasn't changed state |
philpem@91 | 216 | if (!ks->update_flag) return; |
philpem@91 | 217 | |
philpem@96 | 218 | |
philpem@80 | 219 | // if buffer empty, do a keyboard scan |
philpem@80 | 220 | if (ks->buflen == 0) { |
philpem@84 | 221 | // Keyboard Data Begins Here (BEGKBD) |
philpem@96 | 222 | //ks->buffer[ks->writep] = KEY_BEGIN_KEYBOARD; |
philpem@96 | 223 | //ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; |
philpem@96 | 224 | //if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; |
philpem@84 | 225 | |
philpem@80 | 226 | for (int i=0; i<(sizeof(ks->keystate)/sizeof(ks->keystate[0])); i++) { |
philpem@80 | 227 | if (ks->keystate[i]) { |
philpem@99 | 228 | LOG_IF(kbc_debug, "KBC KEY DOWN: %d\n", i); |
philpem@80 | 229 | ks->buffer[ks->writep] = i; |
philpem@80 | 230 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; |
philpem@84 | 231 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; |
philpem@84 | 232 | nkeys++; |
philpem@80 | 233 | } |
philpem@80 | 234 | } |
philpem@96 | 235 | if (nkeys) { |
philpem@96 | 236 | ks->buffer[ks->writep - 1] |= 0x80; |
philpem@96 | 237 | }else{ |
philpem@96 | 238 | // If no keys down, then send All Keys Up byte |
philpem@99 | 239 | LOG_IFS(kbc_debug, "KBC ALL KEYS UP\n"); |
philpem@91 | 240 | ks->buffer[ks->writep] = KEY_ALL_UP; |
philpem@84 | 241 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; |
philpem@84 | 242 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; |
philpem@84 | 243 | } |
philpem@84 | 244 | |
philpem@82 | 245 | // TODO: inject "mouse data follows" chunk header and mouse movement info |
philpem@84 | 246 | |
philpem@74 | 247 | } |
philpem@91 | 248 | |
philpem@91 | 249 | // Clear the update flag |
philpem@91 | 250 | ks->update_flag = false; |
philpem@74 | 251 | } |
philpem@74 | 252 | |
philpem@80 | 253 | bool keyboard_get_irq(KEYBOARD_STATE *ks) |
philpem@80 | 254 | { |
philpem@80 | 255 | bool irq_status = false; |
philpem@80 | 256 | |
philpem@80 | 257 | // Conditions which may cause an IRQ :- |
philpem@80 | 258 | // Read Data Reg has data and RxIRQ enabled |
philpem@80 | 259 | if (ks->rxie) |
philpem@80 | 260 | if (ks->buflen > 0) irq_status = true; |
philpem@80 | 261 | |
philpem@80 | 262 | // Transmit Data Reg empty and TxIRQ enabled |
philpem@80 | 263 | // if (ks->txie) |
philpem@80 | 264 | |
philpem@80 | 265 | // DCD set and RxIRQ enabled |
philpem@80 | 266 | // |
philpem@80 | 267 | |
philpem@80 | 268 | // returns interrupt status -- i.e. is there data in the buffer? |
philpem@80 | 269 | return irq_status; |
philpem@80 | 270 | } |
philpem@80 | 271 | |
philpem@80 | 272 | uint8_t keyboard_read(KEYBOARD_STATE *ks, uint8_t addr) |
philpem@74 | 273 | { |
philpem@80 | 274 | if ((addr & 1) == 0) { |
philpem@80 | 275 | // Status register -- RS=0, read |
philpem@84 | 276 | uint8_t sr = 0; |
philpem@84 | 277 | if (ks->buflen > 0) sr |= 1; // SR0: a new character has been received |
philpem@84 | 278 | sr |= 2; // SR1: Transmitter Data Register Empty |
philpem@84 | 279 | // 0 + // SR2: Data Carrier Detect |
philpem@84 | 280 | // 0 + // SR3: Clear To Send |
philpem@84 | 281 | // 0 + // SR4: Framing Error |
philpem@84 | 282 | // 0 + // SR5: Receiver Overrun |
philpem@84 | 283 | // 0 + // SR6: Parity Error |
philpem@84 | 284 | if (keyboard_get_irq(ks)) sr |= 0x80; // SR7: IRQ status |
philpem@99 | 285 | //LOG_IF(kbc_debug, "KBC DBG: sr=%02X\n", sr); |
philpem@84 | 286 | return sr; |
philpem@80 | 287 | } else { |
philpem@80 | 288 | // return data, pop off the fifo |
philpem@80 | 289 | uint8_t x = ks->buffer[ks->readp]; |
philpem@80 | 290 | ks->readp = (ks->readp + 1) % KEYBOARD_BUFFER_SIZE; |
philpem@84 | 291 | if (ks->buflen > 0) ks->buflen--; |
philpem@99 | 292 | //LOG_IF(kbc_debug, "\tKBC DBG: rxd=%02X\n", x); |
philpem@80 | 293 | return x; |
philpem@80 | 294 | } |
philpem@74 | 295 | } |
philpem@80 | 296 | |
philpem@80 | 297 | void keyboard_write(KEYBOARD_STATE *ks, uint8_t addr, uint8_t val) |
philpem@80 | 298 | { |
philpem@80 | 299 | if ((addr & 1) == 0) { |
philpem@80 | 300 | // write to control register |
philpem@80 | 301 | // transmit intr enabled when CR6,5 = 01 |
philpem@80 | 302 | // receive intr enabled when CR7 = 1 |
philpem@80 | 303 | |
philpem@80 | 304 | // CR0,1 = divider registers. When =11, do a software reset |
philpem@80 | 305 | if ((val & 3) == 3) { |
philpem@80 | 306 | ks->readp = ks->writep = ks->buflen = 0; |
philpem@80 | 307 | } |
philpem@80 | 308 | |
philpem@80 | 309 | // Ignore CR2,3,4 (word length)... |
philpem@80 | 310 | |
philpem@80 | 311 | // CR5,6 = Transmit Mode |
philpem@80 | 312 | ks->txie = (val & 0x60)==0x20; |
philpem@80 | 313 | |
philpem@80 | 314 | // CR7 = Receive Interrupt Enable |
philpem@80 | 315 | ks->rxie = (val & 0x80)==0x80; |
philpem@80 | 316 | } else { |
philpem@80 | 317 | // Write command to KBC -- TODO! |
philpem@91 | 318 | if (val == KEY_CMD_RESET) { |
philpem@99 | 319 | LOG_IFS(kbc_debug, "KBC: KEYBOARD RESET!\n"); |
philpem@91 | 320 | ks->readp = ks->writep = ks->buflen = 0; |
philpem@99 | 321 | } else { |
philpem@99 | 322 | LOG("KBC TODO: write keyboard data 0x%02X\n", val); |
philpem@91 | 323 | } |
philpem@80 | 324 | } |
philpem@80 | 325 | } |
philpem@80 | 326 |