CHIP SN8F2288 //{{SONIX_CODE_OPTION ; Options for Lenovo Compact Keyboard .Code_Option Fcpu "Fosc/4" .Code_Option Fslow "Flosc/2" .Code_Option High_CLK "12M_X'tal" .Code_Option LVD "LVD_M" .Code_Option Reset_Pin "P07" .Code_Option Rst_Length "No" .Code_Option Security "Enable" .Code_Option Watch_Dog "Enable" //}}SONIX_CODE_OPTION .DATA _canary_check EQU 0x2880 _flasher EQU 0x2890 UTX EQU P0.6 ; S15 URX EQU P0.5 ; S10 S0 EQU P2.2 S0M EQU P2M.2 S1 EQU P2.0 S1M EQU P2M.0 S2 EQU P4.0 S2M EQU P4M.0 S3 EQU P4.5 S3M EQU P4M.5 S4 EQU P4.1 S4M EQU P4M.1 S5 EQU P2.1 S5M EQU P2M.1 S6 EQU P4.4 S6M EQU P4M.4 S7 EQU P4.2 S7M EQU P4M.2 S8 EQU P4.3 S8M EQU P4M.3 S9 EQU P2.3 S9M EQU P2M.3 S10 EQU P0.5 S10M EQU P0M.5 S11 EQU P0.4 S11M EQU P0M.4 S12 EQU P4.6 S12M EQU P4M.6 S13 EQU P4.7 S13M EQU P4M.7 S14 EQU P0.3 S14M EQU P0M.3 S15 EQU P0.6 S15M EQU P0M.6 R0 EQU P1.0 R0M EQU P1M.0 R1 EQU P1.7 R1M EQU P1M.7 R2 EQU P1.2 R2M EQU P1M.2 R3 EQU P1.1 R3M EQU P1M.1 R4 EQU P1.4 R4M EQU P1M.4 R5 EQU P1.3 R5M EQU P1M.3 R6 EQU P1.5 R6M EQU P1M.5 R7 EQU P1.6 R7M EQU P1M.6 EP0_BYTES EQU 8 EP1_BYTES EQU 8 EP2_BYTES EQU 8 bmRequestType DS 1 bRequest DS 1 wValueLo DS 1 wValueHi DS 1 wIndexLo DS 1 wIndexHi DS 1 wLengthLo DS 1 wLengthHi DS 1 txPtrLo DS 1 txPtrHi DS 1 txSizeLo DS 1 txSizeHi DS 1 txPktLen DS 1 txTmpCnt DS 1 usbState DS 1 usbStateSetupInvalid EQU usbState.0 usbStateSetAddress EQU usbState.1 usbStateIn0Done EQU usbState.3 usbStateZeroPad EQU usbState.4 usbStateStringCnt EQU usbState.5 usbStateCopyRAM EQU usbState.6 usbStateHTDData EQU usbState.7 usbState2 DS 1 usbStateAddressValid EQU usbState2.0 usbStateEnterFlasher EQU usbState2.1 xlatVal1 DS 1 xlatVal2 DS 1 kbdRow DS 1 kbdRowsLow DS 1 kbdSense1 DS 1 kbdSense2 DS 1 .CODE ORG 0x0 ; Reset vector ; Jump to bootloader, checks canary and continues execution at 0x10 if found JMP _canary_check ORG 0x8 ; Interrupt vector JMP _flasher ORG 0x10 ; Bootloader jumps here on successful canary check, start of payload execution _start: ; Set stack pointer and disable interrupts MOV A, #7 B0MOV STKP, A MOV A, #0 B0MOV RBANK, A ; Jump into bootloader if watchdog triggered or undefined reset source B0BTS1 FNT0 JMP _flasher ; NT0 == 0 => watchdog reset or undefined reason ; Light up the power LED (P5.3/PWM0) B0BCLR P5.3 ; Set to low level to light up B0BSET P5M.3 ; Set to output ; DS indicates watchdog may start running before CPU ; Tickle it once so we have a well-defined time left MOV A, #0x5a B0MOV WDTR, A ; Set up P0.6/UTX (UART TX) ; FIXME: PnUR is write-only, B0BSET/B0BCLR are likely broken B0BSET P0UR.6 ; Enable pull-up (UART idle is high) B0BCLR P0M.6 ; Set to input (TXEN will override to output) MOV A, #0x60 ; Baud 115200 B0MOV URBRC, A MOV A, #0x90 ; 24MHz clock, TX enabled, 8n1, 1-byte mode B0MOV URTX, A ; Send a message that we are alive MOV A, #'H' CALL _uart_tx MOV A, #'i' CALL _uart_tx MOV A, #'!' CALL _uart_tx MOV A, #13 CALL _uart_tx MOV A, #10 CALL _uart_tx ; Jump into bootloader if "Return" key (S10/R6) is held B0BSET P0M.5 ; Set S10 to output B0BCLR P1M.5 ; Set R6 to input B0BSET P1UR.5 ; Enable pull-up on R6 B0BSET S10 ; Set S10 high CALL _delayshort B0BTS1 R6 ; Jump if R6 is low JMP @F B0BCLR S10 ; Set S10 low CALL _delayshort B0BTS1 R6 ; Jump if R6 is low JMP _return_held @@: ; Jump into bootloader if P0.5/P0.6 are shorted (URX/UTX) B0BCLR 0xa9.4 ; Switch UTX back to GPIO B0BSET P0M.6 ; Set UTX to output B0BCLR P0M.5 ; Set URX to input B0BSET P0UR.5 ; Enable pull-up on URX B0BSET UTX ; Set P0.6/UTX high CALL _delayshort B0BTS1 URX ; Jump if P0.5/URX is low JMP @F B0BCLR UTX ; Set P0.6/UTX low CALL _delayshort B0BTS1 URX ; Jump if P0.5/URX is low JMP _uart_shorted @@: B0BSET 0xa9.4 ; Switch UTX back to UART ; Reset either from undervoltage (power-on) or external reset ; Cold reset state per datasheet: ; - Clock is 12MHz PLL synced to external oscillator ; - IOs set to input ; Setup GPIO CALL _gpio_init ; Setup USB registers CALL _usb_init _mainloop: ; Tickle watchdog MOV A, #0x5a B0MOV WDTR, A @@: B0BTS0 FEP0SETUP ; Jump if SETUP packet rx'd JMP _usb_setup B0BTS0 FEP0OUT JMP _usb_ep0_out B0BTS0 FEP0IN JMP _usb_ep0_in B0BTS0 FBUS_RST JMP _usb_reset B0BTS0 FSOF JMP _usb_sof JMP @B _usb_sof: B0BCLR FSOF ; Check for cross-talk from too many depressed keys CALL _kbd_count_rows_low ; Read the columns CALL _kbd_sense ; Release the scanned row CALL _kbd_set_row_input ; Increment row INCMS kbdRow MOV A, #0x07 AND kbdRow, A ; Lower the next row CALL _kbd_set_row_output B0MOV A, kbdSense1 OR A, kbdSense2 B0BTS0 FZ ; Jump if kbdSense is zero JMP _mainloop MOV A, #'K' CALL _uart_tx B0MOV A, kbdRow CALL _uart_hex B0MOV A, kbdSense1 CALL _uart_hex B0MOV A, kbdSense2 CALL _uart_hex JMP _mainloop _usb_reset: MOV A, #13 CALL _uart_tx MOV A, #10 CALL _uart_tx MOV A, #'R' CALL _uart_tx CALL _usb_init ; Wait until reset is de-asserted _usb_reset_wait: B0BTS0 FBUS_RST JMP _usb_reset_wait JMP _mainloop _gpio_init: ; Enable all pull-ups MOV A, #0xff B0MOV P0UR, A B0MOV P1UR, A B0MOV P2UR, A B0MOV P4UR, A B0MOV P5UR, A ; Switch all to input MOV A, #0x00 B0MOV P0M, A B0MOV P1M, A B0MOV P2M, A B0MOV P4M, A B0MOV P5M, A ; Set port output data to 0 B0MOV P0, A B0MOV P1, A B0MOV P2, A B0MOV P4, A B0MOV P5, A ; Reset kbd scan state B0MOV kbdRow, A ; Re-light LED B0BCLR P5.3 ; Set to low level to light up B0BSET P5M.3 ; Set to output ;; Set UTX/URX to open-drain ;MOV A, #0x0c ;B0MOV P1OC, A RET _usb_init: MOV A, #0 B0MOV usbState, A B0MOV usbState2, A B0MOV USTATUS, A MOV A, #0x80 B0MOV UDA, A ; Set address to 0 and enable B0MOV UE1R, A ; Enable EP1, set to NAK B0MOV UE2R, A ; Enable EP2, set to NAK B0BSET FDP_PU_EN ; Enable D+ pull-up B0BSET FSOF_INT_EN ; Enable SOF interrupt request RET _kbd_sense: MOV A, #0 B0MOV kbdSense1, A B0MOV kbdSense2, A B0BTS1 S0 B0BSET kbdSense1.0 B0BTS1 S1 B0BSET kbdSense1.1 B0BTS1 S2 B0BSET kbdSense1.2 B0BTS1 S3 B0BSET kbdSense1.3 B0BTS1 S4 B0BSET kbdSense1.4 B0BTS1 S5 B0BSET kbdSense1.5 B0BTS1 S6 B0BSET kbdSense1.6 B0BTS1 S7 B0BSET kbdSense1.7 B0BTS1 S8 B0BSET kbdSense2.0 B0BTS1 S9 B0BSET kbdSense2.1 B0BTS1 S10 B0BSET kbdSense2.2 B0BTS1 S11 B0BSET kbdSense2.3 B0BTS1 S12 B0BSET kbdSense2.4 B0BTS1 S13 B0BSET kbdSense2.5 B0BTS1 S14 B0BSET kbdSense2.6 B0BTS1 S15 B0BSET kbdSense2.7 RET _kbd_set_row_output: B0MOV A, kbdRow AND A, #0x7 MOV R, A ADD A, R B0ADD PCL, A B0BSET R0M RET B0BSET R1M RET B0BSET R2M RET B0BSET R3M RET B0BSET R4M RET B0BSET R5M RET B0BSET R6M RET B0BSET R7M RET _kbd_set_row_input: B0MOV A, kbdRow AND A, #0x7 MOV R, A ADD A, R B0ADD PCL, A B0BCLR R0M RET B0BCLR R1M RET B0BCLR R2M RET B0BCLR R3M RET B0BCLR R4M RET B0BCLR R5M RET B0BCLR R6M RET B0BCLR R7M RET _kbd_count_rows_low: MOV A, #0 B0MOV kbdRowsLow, A B0BTS1 R0 INCMS kbdRowsLow B0BTS1 R1 INCMS kbdRowsLow B0BTS1 R2 INCMS kbdRowsLow B0BTS1 R3 INCMS kbdRowsLow B0BTS1 R4 INCMS kbdRowsLow B0BTS1 R5 INCMS kbdRowsLow B0BTS1 R6 INCMS kbdRowsLow B0BTS1 R7 INCMS kbdRowsLow NOP RET _usb_ep0_in: B0BCLR FEP0IN ; Early ack of IN irq, we can't get a new one until EP0 NAK state is cleared MOV A, #'i' CALL _uart_tx CALL _usb_write_ep0 B0BTS0 usbStateSetAddress JMP _usb_ep0_set_addr JMP _mainloop _usb_ep0_set_addr: B0BSET wValueLo.7 B0MOV A, wValueLo B0MOV UDA, A ; Update address register MOV A, #'A' CALL _uart_tx B0MOV A, wValueLo CALL _uart_hex JMP _mainloop _usb_ep0_out: B0BCLR FEP0OUT ; Ack OUT irq MOV A, #'o' CALL _uart_tx B0MOV A, EP0OUT_CNT B0BTS1 FZ ; Call if non-zero CALL _uart_hex B0BTS0 usbStateHTDData JMP _usb_setup_htd_got_data JMP _mainloop _setup_dispatch_table: DW 0x0005 ; SET_ADDRESS JMP _usb_htd_set_address DW 0x0009 ; SET_CONFIGURATION JMP _usb_htd_set_configuration DW 0x2109 ; HID SET_REPORT JMP _usb_htd_hid_set_report DW 0x210a ; HID SET_IDLE JMP _usb_htd_hid_set_idle DW 0x210b ; HID SET_PROTOCOL JMP _usb_setup_default DW 0x8000 ; GET_STATUS JMP _usb_dth_get_status DW 0x8006 ; GET_DESCRIPTOR (device) JMP _usb_dth_get_device_descriptor DW 0x8106 ; GET_DESCRIPTOR (interface) JMP _usb_dth_get_interface_descriptor DW 0xa101 ; HID GET_REPORT JMP _usb_dth_hid_get_report DW 0xa102 ; HID GET_IDLE JMP _usb_setup_default DW 0xa103 ; HID GET_PROTOCOL JMP _usb_setup_default DW 0xFFFF JMP _usb_setup_default _usb_setup_default: B0BSET usbStateSetupInvalid MOV A, #'?' CALL _uart_tx B0MOV A, bmRequestType CALL _uart_hex B0MOV A, bRequest CALL _uart_hex RET _usb_dth_get_device_descriptor: B0MOV A, wValueHi ADD A, #0xfc B0BTS0 FC JMP _usb_get_desc_badidx B0MOV A, wValueHi B0ADD PCL, A JMP _usb_get_desc_badidx JMP _usb_get_desc_device JMP _usb_get_desc_config JMP _usb_get_desc_string _usb_dth_get_interface_descriptor: B0MOV A, wValueHi SUB A, #0x20 ADD A, #0xfc B0BTS0 FC JMP _usb_get_desc_badidx B0MOV A, wValueHi SUB A, #0x20 B0ADD PCL, A JMP _usb_get_desc_badidx JMP _usb_get_desc_hid JMP _usb_get_desc_report JMP _usb_get_desc_physical _usb_get_desc_badidx: MOV A, #'d' CALL _uart_tx B0MOV A, wValueHi CALL _uart_hex B0MOV A, wValueLo CALL _uart_hex B0BSET usbStateSetupInvalid RET _device_descriptor: DB 0x12 ; bLength DB 1 ; bDescriptorType (DEVICE) DB 0x00, 0x02 ; bcdUSB (2.00) DB 0, 0, 0 ; Class/Subclass/Protocol DB EP0_BYTES ; EP0 max packet size DB 0xef, 0x17 ; Vendor 0x17ef (Lenovo) DB 0x47, 0x60 ; Device 0x6047 (Lenovo ThinkPad Compact Keyboard with TrackPoint) DB 0x00, 0x01 ; bcdDevice (1.0) DB 1 ; iManufacturer (String 1) DB 2 ; iProduct (String 2) DB 0 ; iSerial (n/a) DB 1 ; bNumConfigurations (1) _device_descriptor_end: _configuration_descriptor: DB 9 ; bLength DB 2 ; bDescriptorType (CONFIGURATION) DB 0x32, 0x00 ; wTotalLength DB 2 ; bNumInterfaces DB 1 ; bConfigurationValue DB 0 ; iConfiguration (n/a) DB 0xa0 ; bmAttributes (BUS_POWERED, REMOTE_WAKEUP) DB 50 ; bMaxPower (100mA) ; Keyboard interface DB 9 ; bLength DB 4 ; bDescriptorType (INTERFACE) DB 0 ; bInterfaceNumber DB 0 ; bAlternateSetting DB 1 ; bNumEndpoints DB 3 ; bInterfaceClass (HID) DB 1 ; bInterfaceSubClass (boot interface) DB 1 ; bInterfaceProtocol (keyboard) DB 0 ; iInterface (n/a) DB 9 ; bLength DB 0x21 ; bDescriptorType (HID) DB 0x00, 0x01 ; bcdHID (1.00) DB 0 ; bCountryCode (Not Supported) DB 1 ; bNumDescriptors DB 0x22 ; bDescriptorType (Report) DB 63, 0 ; wDescriptorLength (63) DB 7 ; bLength DB 5 ; bDescriptorType (ENDPOINT) DB 0x81 ; bEndpointAddress (EP1 IN) DB 0x03 ; bmAttributes (Interrupt, Data) DB 0x3f, 0x00 ; wMaxPacketSize (63 bytes) DB 10 ; bInterval (10ms) ; Mouse interface DB 9 ; bLength DB 4 ; bDescriptorType (INTERFACE) DB 1 ; bInterfaceNumber DB 0 ; bAlternateSetting DB 1 ; bNumEndpoints DB 3 ; bInterfaceClass (HID) DB 1 ; bInterfaceSubClass (boot interface) DB 2 ; bInterfaceProtocol (mouse) DB 0 ; iInterface (n/a) ; FIXME: Add Mouse HID descriptor once Keyboard is working. DB 7 ; bLength DB 5 ; bDescriptorType (ENDPOINT) DB 0x82 ; bEndpointAddress (EP2 IN) DB 0x03 ; bmAttributes (Interrupt, Data) DB 0x3f, 0x00 ; wMaxPacketSize (63 bytes) DB 10 ; bInterval (10ms) _configuration_descriptor_end: _string_langids: DB 4, 3, 9, 4 _string_mfg: DB 14, 3 DW "Lenovo" _string_product: DB 92, 3 DW "ThinkPad Compact USB Keyboard with TrackPoint" _usb_status_ok: DW 0 _kbd_hid_descriptor: DB 9 ; bLength DB 0x21 ; bDescriptorType (HID) DB 0x00, 0x01 ; bcdHID (1.00) DB 0 ; bCountryCode (Not Supported) DB 1 ; bNumDescriptors DB 0x22 ; bDescriptorType (Report) DB 63, 0 ; wDescriptorLength (63) _kbd_hid_descriptor_end: _kbd_report_descriptor: ; Boot-compatible descriptor DB 0x05, 1 ; Usage Page (Generic Desktop) DB 0x09, 6 ; Usage (Keyboard) DB 0xa1, 1 ; Collection (Application) ; Modifier keys DB 0x05, 7 ; Usage Page (Key Codes) DB 0x19, 224 ; Usage Minimum (224) DB 0x29, 231 ; Usage Maximum (231) DB 0x15, 0 ; Logical Minimum (0) DB 0x25, 1 ; Logical Minimum (1) DB 0x75, 1 ; Report Size (1) DB 0x95, 8 ; Report Count (8) DB 0x81, 2 ; Input (Data, Variable, Absolute) ; Reserved byte DB 0x95, 1 ; Report Count (1) DB 0x75, 8 ; Report Size (8) DB 0x81, 1 ; Input (Constant, Absolute) ; LED byte (5 bits + 3 bits padding) DB 0x95, 5 ; Report Count (5) DB 0x75, 1 ; Report Size (1) DB 0x05, 8 ; Usage Page (LEDs) DB 0x19, 1 ; Usage Minimum (1) DB 0x29, 5 ; Usage Maximum (5) DB 0x91, 2 ; Output (Data, Variable, Absolute) DB 0x95, 1 ; Report Count (1) DB 0x75, 3 ; Report Size (3) DB 0x91, 1 ; Output (Data, Variable, Absolute) ; 6 key code bytes DB 0x95, 6 ; Report Count (6) DB 0x75, 8 ; Report Size (8) DB 0x15, 0 ; Logical Minimum (0) DB 0x25, 175 ; Logical Maximum (175) DB 0x05, 7 ; Usage Page (Key Codes) DB 0x19, 0 ; Usage Minimum (0) DB 0x29, 175 ; Usage Maximum (175) DB 0x81, 0 ; Input (Data, Array) DB 0xc0 ; End Collection (Application) _kbd_report_descriptor_end: _clamp_size: B0MOV A, wLengthLo SUB A, txSizeLo B0MOV A, wLengthHi SBC A, txSizeHi B0BTS0 FC ; Return if carry set RET B0MOV A, wLengthLo B0MOV txSizeLo, A B0MOV A, wLengthHi B0MOV txSizeHi, A RET _usb_get_desc_device: MOV A, #'D' CALL _uart_tx MOV A, #_device_descriptor$M B0MOV txPtrHi, A MOV A, #_device_descriptor$L B0MOV txPtrLo, A MOV A, #0x12 B0MOV txSizeLo, A _usb_get_desc: CALL _clamp_size CALL _usb_write_ep0 RET _usb_get_desc_config: MOV A, #'C' CALL _uart_tx MOV A, #_configuration_descriptor$M B0MOV txPtrHi, A MOV A, #_configuration_descriptor$L B0MOV txPtrLo, A MOV A, #0x32 B0MOV txSizeLo, A JMP _usb_get_desc _usb_get_desc_string: B0MOV A, wValueLo ADD A, #0xfd B0BTS0 FC JMP _usb_get_desc_badidx B0MOV A, wValueLo B0ADD PCL, A JMP _usb_get_string_langids JMP _usb_get_string_mfg JMP _usb_get_string_product _usb_get_string_langids: MOV A, #_string_langids$M B0MOV txPtrHi, A MOV A, #_string_langids$L B0MOV txPtrLo, A MOV A, #4 B0MOV txSizeLo, A JMP _usb_get_desc _usb_get_string_mfg: MOV A, #_string_mfg$M B0MOV txPtrHi, A MOV A, #_string_mfg$L B0MOV txPtrLo, A MOV A, #14 B0MOV txSizeLo, A JMP _usb_get_desc _usb_get_string_product: MOV A, #_string_product$M B0MOV txPtrHi, A MOV A, #_string_product$L B0MOV txPtrLo, A MOV A, #92 B0MOV txSizeLo, A JMP _usb_get_desc _usb_get_desc_hid: _usb_get_desc_report: _usb_get_desc_physical: MOV A, #_kbd_report_descriptor$M B0MOV txPtrHi, A MOV A, #_kbd_report_descriptor$L B0MOV txPtrLo, A MOV A, #63 B0MOV txSizeLo, A JMP _usb_get_desc _usb_write_ep0: MOV A, #'W' CALL _uart_tx ; Check if this is a 0byte-write and bail out early B0MOV A, txSizeLo OR A, txSizeHi B0BTS0 FZ ; Jump if zero JMP _write_empty_to_ep0 ; Clamp packet byte count to EP0 size B0MOV A, txSizeHi B0BTS1 FZ ; Jump if not zero JMP _write_to_ep0_big MOV A, txSizeLo B0MOV R, A MOV A, #EP0_BYTES SUB A, txSizeLo B0BTS1 FC ; Skip if carry _write_to_ep0_big: B0MOV R, #EP0_BYTES _write_to_ep0_copy: ; Save packet size fo updating UE0R later MOV A, R B0MOV txPktLen, A B0MOV txTmpCnt, A ; Copy data into EP0 xmit buffer MOV A, #0 B0MOV UDP0, A _write_to_ep0_copy_loop: B0MOV A, txPtrHi B0MOV Y, A B0MOV A, txPtrLo B0MOV Z, A MOVC B0MOV UDR0_W, A INCMS UDP0 B0MOV A, R B0MOV UDR0_W, A INCMS UDP0 INCMS txPtrLo JMP @F INCMS txPtrHi @@: DECMS txTmpCnt JMP @F JMP _write_to_ep0_update_len @@: DECMS txTmpCnt JMP _write_to_ep0_copy_loop _write_to_ep0_update_len: B0MOV A, txPktLen XCH A, txSizeLo SUB txSizeLo, A ; M = A - M MOV A, #0 XCH A, txSizeHi SBC txSizeHi, A ; M = A - M - /C B0MOV A, txPktLen OR A, #0x20 ; Set to ACK for B0MOV UE0R, A B0MOV A, txPktLen CALL _uart_hex B0MOV A, txPktLen B0BTS0 FZ ; Skip if not zero _write_done: B0BSET usbStateIn0Done _write_not_yet_done: RET _write_empty_to_ep0: B0MOV txPktLen, A B0BTS1 usbStateIn0Done ; Jump if unset JMP _write_to_ep0_update_len MOV A, #'s' CALL _uart_tx JMP _write_to_ep0_update_len ;B0BSET FUE0M1 ; Stall EP0 (FUE0M0 doesn't matter) ;RET _usb_dth_get_status: MOV A, #_usb_status_ok$M B0MOV txPtrHi, A MOV A, #_usb_status_ok$L B0MOV txPtrLo, A MOV A, #2 B0MOV txSizeLo, A JMP _usb_write_ep0 _usb_htd_set_configuration: ; Only one configuration, nothing to do. MOV A, #0x20 ; ACK with no TX B0MOV UE0R, A RET _usb_htd_set_address: MOV A, #'a' CALL _uart_tx B0BSET usbStateSetAddress MOV A, #0x20 ; ACK with no TX B0MOV UE0R, A RET _usb_htd_hid_set_idle: MOV A, #'I' CALL _uart_tx MOV A, #0x20 ; ACK with no TX B0MOV UE0R, A RET _usb_htd_hid_set_report: MOV A, #'r' CALL _uart_tx B0MOV A, EP0OUT_CNT CALL _uart_hex ; First, set the "enter flasher bit" B0BSET usbStateEnterFlasher ; Check each bit against the expected pattern and clear the flag if mismatch B0MOV A, EP0OUT_CNT CMPRS A, #0x08 B0BCLR usbStateEnterFlasher MOV A, #0 B0MOV UDP0, A B0MOV A, UDR0_R CMPRS A, #0xaa B0BCLR usbStateEnterFlasher CALL _uart_hex INCMS UDP0 B0MOV A, UDR0_R CMPRS A, #0x55 B0BCLR usbStateEnterFlasher CALL _uart_hex INCMS UDP0 B0MOV A, UDR0_R CMPRS A, #0xa5 B0BCLR usbStateEnterFlasher CALL _uart_hex INCMS UDP0 B0MOV A, UDR0_R CMPRS A, #0x5a B0BCLR usbStateEnterFlasher CALL _uart_hex MOV A, #0x20 ; ACK with no TX B0MOV UE0R, A ; Enter flasher if flag is still set B0BTS0 usbStateEnterFlasher JMP _flasher RET _usb_dth_hid_get_report: MOV A, #'g' CALL _uart_tx ; Send last 8 bytes in buffer ; FIXME: Handle this properly MOV A, #0x28 B0MOV UE0R, A RET _usb_setup: MOV A, #0 ; Unstall & NAK EP0 IN/OUT B0MOV UE0R, A B0MOV txSizeLo, A B0MOV txSizeHi, A B0MOV usbState, A MOV A, #' ' CALL _uart_tx MOV A, #'S' CALL _uart_tx ; Copy setup packet MOV A, #8 B0MOV R, A B0MOV Y, #bmRequestType$M B0MOV Z, #bmRequestType$L MOV A, #0 B0MOV UDP0, A @@: B0MOV A, UDR0_R B0MOV @YZ, A INCMS UDP0 INCMS Z DECMS R JMP @B NOP ; Log bRequest MOV A, #1 B0MOV UDP0, A B0MOV A, UDR0_R CALL _uart_hex B0BTS0 bmRequestType.7 ; Jump if bit7 set JMP _usb_setup_dth ; host is writing to device, check if there will be OUT with data. MOV A, wLengthLo OR A, wLengthHi B0BTS0 FZ ; Jump if length is 0 JMP _usb_setup_htd_no_data B0BSET FUE0M0 ; Change EP0 to ACK, wait for data B0BSET usbStateHTDData JMP _usb_setup_exit _usb_setup_htd_got_data: _usb_setup_htd_no_data: _usb_setup_dth: B0MOV Y, #_setup_dispatch_table$M B0MOV Z, #_setup_dispatch_table$L _usb_setup_do_dispatch: B0MOV A, bmRequestType B0MOV R, A B0MOV A, bRequest CALL _dispatch B0BTS1 usbStateSetupInvalid JMP _setup_done_valid B0BSET FUE0M1 ; Stall EP0 _setup_done_valid: B0BTS1 bmRequestType.7 ; Skip if bit7 set B0BSET FUE0M0 ; Unnak EP0 IN for final ACK _usb_setup_exit: B0BCLR FEP0SETUP ; Late ack of SETUP irq JMP _mainloop _dispatch: B0MOV xlatVal1, A MOV A, R B0MOV xlatVal2, A JMP _xlat_next _xlat_loop: AND A, R CMPRS A, #0xff ; Jump if last entry JMP @F JMP _xlat_do_indirect_jump @@: CALL _inc_yz ; skip jump target CALL _inc_yz _xlat_next: MOVC ; Read ROM word into R (hi) and A (lo) CMPRS A, xlatVal1 ; Jump if A != xlatVal1 JMP _xlat_loop XCH A, R CMPRS A, xlatVal2 ; Jump if R != xlatVal2 JMP _xlat_loop _xlat_do_indirect_jump: CALL _inc_yz CALL _jmp_yz RET ; never reached, kept for disassembler _jmp_yz: ; FIXME: Interrupts must be disabled ; DS is underspecified, but experimentally it ; looks like if CALL goes from level 0 to level 1, ; then the return PC is stored in STK0H/STK0L ; ; Level STKPB2 STKPB1 STKPB0 HighByte LowByte ; 0 1 1 1 n/a n/a ; 1 1 1 0 STK0H STK0L ; 2 1 0 1 STK1H STK1L ; [...] ; 6 0 0 1 STK5H STK5L ; 7 0 0 0 STK6H STK6L ; 8 1 1 1 STK7H STK7L B0MOV A, STKP AND A, #7 B0ADD PCL, A JMP _set_stack_6 ; STKP 0 / Level 7 JMP _set_stack_5 ; STKP 1 / Level 6 JMP _set_stack_4 ; STKP 2 / Level 5 JMP _set_stack_3 ; STKP 3 / Level 4 JMP _set_stack_2 ; STKP 4 / Level 3 JMP _set_stack_1 ; STKP 5 / Level 2 JMP _set_stack_0 ; STKP 6 / Level 1 JMP _set_stack_7 ; STKP 7 / Level 8 [or 0] _set_stack_0: B0MOV A, Y B0MOV STK0H, A B0MOV A, Z B0MOV STK0L, A RET _set_stack_1: B0MOV A, Y B0MOV STK1H, A B0MOV A, Z B0MOV STK1L, A RET _set_stack_2: B0MOV A, Y B0MOV STK2H, A B0MOV A, Z B0MOV STK2L, A RET _set_stack_3: B0MOV A, Y B0MOV STK3H, A B0MOV A, Z B0MOV STK3L, A RET _set_stack_4: B0MOV A, Y B0MOV STK4H, A B0MOV A, Z B0MOV STK4L, A RET _set_stack_5: B0MOV A, Y B0MOV STK5H, A B0MOV A, Z B0MOV STK5L, A RET _set_stack_6: B0MOV A, Y B0MOV STK6H, A B0MOV A, Z B0MOV STK6L, A RET _set_stack_7: B0MOV A, Y B0MOV STK7H, A B0MOV A, Z B0MOV STK7L, A RET _return_held: MOV A, #'E' CALL _uart_tx MOV A, #'r' CALL _uart_tx JMP _flasher _uart_shorted: B0BSET 0xa9.4 ; Switch UTX back to UART MOV A, #'E' CALL _uart_tx MOV A, #'s' CALL _uart_tx JMP _flasher _inc_yz: INCMS Z JMP @F INCMS Y RET @@: RET _uart_hex: MOV R, A SWAP R CALL _uart_nibble MOV A, R ; fall-through _uart_nibble: AND A, #0xf ADD A, #0xf6 ; -0xa B0BTS0 FC ; Skip next insn if carry unset ADD A, #0x27 ; 'a' - '0' - 0xa ADD A, #0x3a ; '0' + 0xa ; fall-through _uart_tx: B0MOV URTXD1, A @@: B0BTS1 FUTTXIRQ ; Check if TX is done JMP @B B0BCLR FUTTXIRQ RET _delayshort: MOV A, #0 B0MOV R, A @@: DECMS R JMP @B RET ORG 0x27ff DW 0xaaaa ; canary ORG _canary_check JMP _start ORG _flasher JMP $