/** @file debug_comm.S * @brief GDB Server communications support routines * */ /* Copyright (C) 2007-2011 the NxOS developers * * Module Developed by: TC Wan * * See AUTHORS for a full list of the developers. * * See COPYING for redistribution license * */ #define __ASSEMBLY__ #include "debug_macros.h" #include "debug_stub.h" #include "debug_internals.h" #include "debug_comm.h" .extern dbg__sendCommMsg /* Hexutils function references */ .extern hex2char .extern char2hex .extern byte2ascii .extern halfword2ascii_be .extern halfword2ascii_le .extern word2ascii_be .extern word2ascii_le .extern ascii2hex_varlen_be .extern ascii2byte .extern ascii2halfword_be .extern ascii2halfword_le .extern ascii2word_be .extern ascii2word_le .bss .align 4 .global debug_InCommBuf .global debug_OutCommBuf debug_InCommBuf: .space USB_BUFSIZE,0 debug_OutCommBuf: .space USB_BUFSIZE,0 debug_msgRxBufPtr: .word 0x0 debug_msgTxBufPtr: .word 0x0 debug_msgRxBuf_AppendPtr: .word 0x0 debug_msgTxBuf_AppendPtr: .word 0x0 .equ RXAPPENDPTR_OFFSET, (debug_msgRxBuf_AppendPtr - debug_msgRxBufPtr) .equ TXAPPENDPTR_OFFSET, (debug_msgTxBuf_AppendPtr - debug_msgTxBufPtr) debug_segmentRxNum: /* Current Rx Segment Number */ .word 0x0 /* Comm Channel and NXT Received Message Length is now common to both NxOS and NXT Firmware */ debug_nxtMsgLength: .word 0x0 .global debug_nxtCommChannel debug_nxtCommChannel: .word 0x0 .global debug_nxtCommOverrun debug_nxtCommOverrun: .word 0x0 .equ NXTCOMMCHANNEL_OFFSET, (debug_nxtCommChannel - debug_nxtMsgLength) .equ NXTCOMMOVERRUN_OFFSET, (debug_nxtCommOverrun - debug_nxtMsgLength) .data .align 4 nxt_commcmd_header: .byte NXT_GDBMSG_TELEGRAMTYPE, 0x00, 0x00 /* padded to 3 bytes */ .code 32 .text .align 4 /* Debugger Communications Routines * It does not make sense to pass information from the Debugger Module to the Comm. link one character * at a time, especially if we're not using a native serial interface (e.g., EIA-232). Consequently * a Message interface has been defined. This can still call getChar() and putChar() subroutines * if so desired, but it'll be a purely internal matter. * * Message Format * Since we need to use EP1&2 (Bulk channels) to communicate with the PC Host, the messages should * follow the NXT Direct Commands message structure (this will allow for interoperability with NXT Firmware * in addition to NxOS). The maximum length of any USB communications via the Bulk channel is 64 bytes. * There is a one byte Telegram Type field which identifies the type of telegram, followed by the * Telegram header and actual message. * * The LEGO Mindstorms Communications Protocol Direct Commands GDB Message format (including all headers) * is as follows: * * GDB Command * =========== * Byte 0: Telegram Type Field (0x8d Direct Command, No response required) | NXT Msg Header * Byte 1: Segment No (1-255, 0: Last Segment; limit is MSG_NUMSEGMENTS) | * Byte 2: Telegram Size (Len of USB Buffer - 3, max is MSG_SEGMENTSIZE) | * Byte 3-N: Message data | GDB Command * * The GDB Command (of size M) has the following format: * Offset 0: '+'/'-' Command Received Status (Optional) * Offset 1/0: '$' * Offset 2/1: GDB Command char * Offset 3 - (M-4): Command packet info * Offset M-3: '#' * Offset M-2: MSB of Checksum * Offset M-1: LSB of Checksum * * To be safe, we assume that the Command Received Status is always sent by the GDB server. Therefore, * The maximum size of a GDB Command packet is MSGBUF_SIZE - 5 ('+'/'-', '$', '#', 2 byte checksum) * * GDB Response * ============ * Byte 0: Telegram Type Field (0x8d Direct Command, No response required) | NXT Msg Header * Byte 1: Segment No (1-255, 0: Last Segment; limit is MSG_NUMSEGMENTS) | * Byte 2: Telegram Size (Len of USB Buffer - 3, max is MSG_SEGMENTSIZE) | * Byte 3-N: Message data | GDB Response * * The GDB Retransmission Request has the following format: * Offset 0: '-' Command Received Status * * The GDB Response (of size M) has the following format: * Offset 0: '+' Command Received Status * Offset 1: '$' * Offset 2 - (M-4): Response packet info * Offset M-3: '#' * Offset M-2: MSB of Checksum * Offset M-1: LSB of Checksum * * The maximum size of a GDB Response packet is MSGBUF_SIZE - 5 ('+', '$', '#', 2 byte checksum) * * Note: The Telegram Size is the actual size of the Message Data portion * (i.e., excludes the three header bytes, includes the GDB Command/Response Packet checksum bytes * in the last segment) */ .global dbg__comm_init /* dbg__comm_init * Initialize communications channel. * On Entry: * R0: MSG Rx Buf Pointer * R1: MSG Tx Buf Pointer */ dbg__comm_init: stmfd sp!, {lr} ldr r2, =debug_msgRxBufPtr stmia r2!, {r0, r1} /* debug_msgRxBufPtr and debug_msgTxBufPtr */ stmia r2!, {r0, r1} /* debug_msgRxBuf_AppendPtr and debug_msgTxBuf_AppendPtr */ bl _dbg__comm_readbuf_reset ldr r1, =debug_nxtMsgLength mov r0, #0 str r0, [r1, #NXTCOMMCHANNEL_OFFSET] /* Clear NXT Channel on INIT */ ldmfd sp!, {pc} _dbg__comm_readbuf_reset: ldr r1, =debug_nxtMsgLength mov r0, #0 str r0, [r1] /* Clear Received Comm Message Length */ bx lr .global dbg__copyNxtDebugMsg /* dbg__copyNxtDebugMsg * Copy NXT Debug Message to our own Buffers, indicate Msg Received status. * Note: This routine is now used by both NXT Firmware and NxOS * On Entry: * R0: NXT Input Buf Pointer * R1: NXT Communications Channel Enum (CmdBit) * R2: NXT Raw Message Length * On Exit: * R0-R3: Destroyed */ dbg__copyNxtDebugMsg: ldr r3, =debug_nxtMsgLength str r1, [r3, #NXTCOMMCHANNEL_OFFSET] /* save Communications Channel first */ ldr r1, [r3] /* Check if there's an unread message in the buffer */ cmp r1, #0 beq cont_dbg__copyNxtDebugMsg /* No unread message, so continue */ exit_dbg__NxtDebugMsgOverrun: ldr r1, [r3, #NXTCOMMOVERRUN_OFFSET] add r1, r1, #1 str r1, [r3, #NXTCOMMOVERRUN_OFFSET] /* update message overrun stats */ b exit_dbg__copyNxtDebugMsg cont_dbg__copyNxtDebugMsg: str r2, [r3] ldr r1, =debug_InCommBuf _dbg_memcpy r1, r0, r2, r3 /* r3: scratch register */ exit_dbg__copyNxtDebugMsg: bx lr /* _dbg_reset_msgTxBuf_AppendPtr * Internal variable to reset pointers. * On Exit: * R0: debug_msgTxBuf_AppendPtr * R1: destroyed */ _dbg_reset_msgTxBuf_AppendPtr: ldr r1, =debug_msgTxBufPtr /* Should not be modified */ ldr r0, [r1] str r0, [r1, #TXAPPENDPTR_OFFSET] mov pc, lr /* _dbg__commHasMsg * Internal Segment Reassembly Routine. * On exit: * r0: !0: (Availale Telegram Message Size), 0: no incoming message/segment * r1: message segment number */ _dbg__commHasMsg: stmfd sp!, {lr} ldr r0, =debug_nxtMsgLength ldr r0, [r0] /* R0 contains the Comm Buffer Size, including the NXT Direct Command Header */ ldr r2, =debug_InCommBuf ldrb r1, [r2, #NXT_MSG_TELEGRAMTYPE_OFFSET] cmp r1, #NXT_GDBMSG_TELEGRAMTYPE bne invalid_CommMsg /* Invalid telegram type, ignore */ ldrb r1, [r2, #NXT_MSG_TELEGRAMSIZE_OFFSET] sub r0, r0, r1 /* Comm Buffer Size - Telegram Size = 3 (header size) */ cmp r0, #NXT_GDBMSG_START /* Start offset is equal to header size */ bne invalid_CommMsg /* Invalid Message Length, ignore */ mov r0, r1 /* Telegram Message Size */ ldrb r1, [r2, #NXT_MSG_SEGNUM_OFFSET] b _exit_dbg__commHasMsg invalid_CommMsg: bl _dbg__comm_readbuf_reset /* Next Comm telegram transaction */ mov r0, #0 _exit_dbg__commHasMsg: ldmfd sp!, {pc} /* _copy_msg_from_commbuf * Internal Comm buffer copy routine, handles segment reassembly. * On entry: * r0: number of bytes to copy * r1: segment number * On exit: * r0: cummulative message length * r1: segment number * r2, r3: Destroyed */ _copy_msg_from_commbuf: stmfd sp!, {r1,r4,r5,r6,lr} movs r4, r0 beq _exit_copy_msg_from_commbuf ldr r6, =debug_msgRxBufPtr /* Address of Pointers */ ldr r5, [r6] /* Rx buffer Start Address */ ldr r2, [r6, #RXAPPENDPTR_OFFSET] /* Append Pointer */ sub r3, r2, r5 /* r3: current length of message */ add r3, r3, r4 /* new cummulative length of message */ cmp r3, #MSGBUF_SIZE movhi r4, #0 /* Buffer overflow! */ strhi r5, [r6, #RXAPPENDPTR_OFFSET] /* Reset AppendPtr to beginning of Rx Buffer */ bhi _exit_copy_msg_from_commbuf ldr r3, =debug_InCommBuf add r3, r3, #NXT_GDBMSG_START _dbg_memcpy r2, r3, r4, r0 /* r2 updated to point to next empty char slot in Rx buffer */ sub r4, r2, r5 /* r4: cummulative length of message */ /* Update debug_msgRxBuf_AppendPtr */ teq r1, #0 /* Check if this is last segment (segment 0) */ streq r5, [r6, #RXAPPENDPTR_OFFSET] /* Reset AppendPtr to beginning of Rx Buffer if so */ strne r2, [r6, #RXAPPENDPTR_OFFSET] /* Otherwise, update Append Pointer to receive next segment */ _exit_copy_msg_from_commbuf: bl _dbg__comm_readbuf_reset /* Next Comm telegram transaction */ mov r0, r4 /* Return cummulative message length in R0 */ ldmfd sp!, {r1,r4,r5,r6,pc} /* Return segment number in R1 */ /* _msgbuf_checksum * Internal routine to calculate checksum character buffer. * On entry: * r0: pointer to character buffer to checksum (assume ASCIIZ terminated) * On exit: * r0: pointer to character buffer after ASCIIZ * r1: checksum (8-bit binary) * r2: message length * r3: destroyed */ _msgbuf_checksum: mov r1, #0 /* clear checksum */ mov r2, #0 /* clear length */ 1: ldrb r3, [r0], #1 /* Iterate through buffer */ add r1, r1, r3 /* cummulative sum of char */ teq r3, #0 addne r2, r2, #1 /* increment message length */ bne 1b /* until ASCIIZ found */ and r1, #BYTE0 /* Modulo 256 */ mov pc, lr .global dbg__getDebugMsg /* dbg__getDebugMsg * Retrieve pending Debugger Message if available (Non-Blocking). * On entry: * No parameters (assume pointers were initialized previously using dbg__comm_init) * On exit: * r0: >0 = Valid GDB Message Length (incl '$', excluding '#' and checksum), * 0 = no valid message (yet), -1 = error * r1: GDB Message Buffer Pointer (incl '$', excluding '#' and checksum) * r2, r3: Destroyed * Note: If GDB Message were returned, it is ASCIIZ terminated, does not include '#' and checksum */ dbg__getDebugMsg: stmfd sp!, {r4,r5,lr} bl _dbg__commHasMsg /* r0: message length, r1: segment number */ teq r0, #0 beq exit_dbg__getDebugMsg /* no new message, exit with R0 = 0 */ ldr r4, =debug_segmentRxNum ldr r2, [r4] /* Get current Segment Number */ add r2, r2, #1 /* Expected Segment Number for comparison */ teq r1, #0 streq r1, [r4] /* Update current Segment Number with 0 since it is the last segment */ beq _hasMsg2Copy cmp r1, #MSG_NUMSEGMENTS /* Segment Number < MSG_NUMSEGMENTS? */ bhs _invalid_segment teq r1, r2 /* Valid Segment Number, check against Expected Segment Number */ beq _hasMsg2Copy /* Segment Number matches Expected Segment Number, update buffers */ _invalid_segment: bl _dbg__comm_readbuf_reset /* Invalid, Next Comm telegram transaction */ mov r0, #0 /* Reset Segment Number */ str r0, [r4] /* Update current Segment Number with 0 to prepare for new message */ b exit_dbg__getMsgError /* Exit with error */ _hasMsg2Copy: str r1, [r4] /* Update current Segment Number */ bl _copy_msg_from_commbuf /* r0: cummulative message length, r1: segment number */ teq r1, #0 movne r0, #0 /* Incomplete message, ignore for now */ bne exit_dbg__getDebugMsg /* Message not complete yet, exit */ /* Check for valid GDB message */ mov r4, r0 /* keep message length in R4, assume to be within MSGBUF_SIZE */ ldr r5, =debug_msgRxBufPtr ldr r5, [r5] /* Rx buffer Start Address */ /* Need to account for Packet Acknowledgement */ 1: ldrb r0, [r5] teq r0, #MSGBUF_CTRLC /* Look for Ctrl-C */ moveq r0, r4 /* If found, set R0 to current message length */ beq exit_dbg__getDebugMsg /* and return */ teq r0, #MSGBUF_NAKCHAR /* Look for '-' */ beq exit_dbg__getMsgError /* FIXME: We can't handle retransmission, flag message error */ teq r0, #MSGBUF_ACKCHAR /* Look for '+' */ addeq r5, r5, #1 /* Adjust Buffer Start Pointer (excl '+') */ subeq r4, r4, #1 /* Adjust Message Length */ beq 1b /* Skip all Packet Acknowledgements */ /* Note: Here we assume that we won't get a single ACK '+' or NAK '-' character message. * If we do, it'll be flagged as an error */ subs r2, r4, #MSGBUF_CHKSUMOFFSET /* Look for '#': Message Length - 3 = '#' offset */ blt exit_dbg__getMsgError /* Message Length is too short, exit with error */ ldrb r0, [r5, r2] teq r0, #MSGBUF_CHKSUMCHAR bne exit_dbg__getMsgError /* No checksum char '#', exit with error */ mov r1, #0 strb r1, [r5, r2] /* Zero out '#' char for checksum calc later */ #ifdef CHECK_GDBSTARTCHAR /* Checked in dbg__bkpt_waitCMD */ ldrb r0, [r5] teq r0, #MSGBUF_STARTCHAR /* Look for '$' */ bne exit_dbg__getMsgError /* No start char '$', exit with error */ #endif add r0, r5, #1 /* Checksum packet data (excl '$') */ bl _msgbuf_checksum /* R2: length (excl '$'), R1: calculated checksum, R0: pointer to checksum in receive buffer */ mov r3, r1 /* Keep calculated checksum in R3 (R1 destroyed by ascii2byte) */ bl ascii2byte /* R0: received checksum, R1: address of next buffer location */ teq r0, r3 /* Compare calculated checksum in R3 against received checksum in R0 */ bne exit_dbg__getMsgError /* Checksums do not match, exit with error */ subeq r0, r4, #MSGBUF_CHKSUMOFFSET /* Update message length (incl '$') as return parameter */ add r2, r2, #1 /* expected message length (from _msgbuf_checksum) */ teq r0, r2 beq exit_dbg__getDebugMsg /* Valid length, return */ exit_dbg__getMsgError: mov r0, #MSGBUF_MSGERROR exit_dbg__getDebugMsg: mov r1, r5 /* Return GDB Message Buffer Pointer in R1 */ ldmfd sp!, {r4,r5,pc} /* _copy_msg_to_commbuf * Internal Comm buffer copy routine, handles segment fragmentation. * On entry: * r0: number of bytes to copy * r1: segment number * On exit: * r0: cummulative message length * r1: segment number * r2, r3: Destroyed */ _copy_msg_to_commbuf: stmfd sp!, {r1,r4,r5,r6,lr} ldr r6, =debug_msgTxBufPtr /* Address of Pointers */ ldr r5, [r6, #TXAPPENDPTR_OFFSET] /* Retrieve Tx Append Pointer */ movs r4, r0 beq _exit_copy_msg_to_commbuf #ifdef CHECK_TXLEN add r0, r4, #NXT_GDBMSG_START /* offset = header size */ cmp r0, #USB_BUFSIZE bhi _exit_copy_msg_to_commbuf /* We let calling routine detect problem (segment number will increment) */ #endif /* Fill in Comm Message Header */ ldr r3, =debug_OutCommBuf mov r2, #NXT_GDBMSG_TELEGRAMTYPE strb r2, [r3], #1 /* Telegram type */ strb r1, [r3], #1 /* Segment Number */ strb r0, [r3], #1 /* Message Length */ mov r2, r5 /* Copy to R2 for updating */ mov r1, r4 /* actual GDB message fragment length (exclude Comm header) */ _dbg_memcpy r3, r2, r1, r0 /* This copies over the message fragment, r3, r2 updated */ mov r5, r2 /* Updated Tx Append Pointer, keep in R5 for now */ add r0, r4, #NXT_GDBMSG_START /* Total Comm Buffer Size for Tx (NXT_GDBMSG_START offset = header size) */ bl dbg__sendCommMsg /* Common interface routine to commnuncations drivers */ cmp r0, #TRUE ldrne r5, [r6, #TXAPPENDPTR_OFFSET] /* Tx failed, Retrieve Original Tx Append Pointer */ streq r5, [r6, #TXAPPENDPTR_OFFSET] /* Tx succeeded, Update Tx Append Pointer to new position */ _exit_copy_msg_to_commbuf: ldr r6, [r6] /* Retrieve Tx Buffer Start Address */ sub r0, r5, r6 /* Return calculated cummulative message length (R0) */ ldmfd sp!, {r1,r4,r5,r6,pc} /* Return segment number in R1 */ .global dbg__putDebugMsg /* dbg__putDebugMsg * Sends Debugger Message from calling routine after appending checksum (Blocking) . * On entry: * No parameters (assume pointers were initialized previously using dbg__comm_init) * On exit: * r0: status (0: success, -1: error) * Note: GDB Message to be sent must be ASCIIZ terminated, does not include '#' and checksum * Response packets start with '+' followed by '$' (2 bytes prefix) */ dbg__putDebugMsg: stmfd sp!, {r4,r5,lr} /* Perform Checksum Calculation */ ldr r5, =debug_msgTxBufPtr /* R5: data structure base pointer */ ldr r4, [r5] /* Tx buffer Start Address */ str r4, [r5, #TXAPPENDPTR_OFFSET] /* Reset Tx buffer Append Pointer */ add r0, r4, #2 /* skip '+' and '$' */ bl _msgbuf_checksum /* R2: length (excl '+' and '$'), R1: calculated checksum, R0: pointer to checksum in tx buffer */ #ifdef CHECK_TXLEN add r2, r2, #2 /* r2: returned length from _msgbuf_checksum, added with prefix length */ sub r3, r0, r4 /* r3: calculated length from pointers (incl. prefix length) */ teq r2, r3 bne exit_dbg__putMsgError #endif mov r3, #MSGBUF_CHKSUMCHAR strb r3, [r0, #-1] /* Insert '#' */ bl byte2ascii /* On return, R0 points to location after checksum bytes, R1 is original pointer to checksum */ sub r4, r0, r4 /* R4 = Calculated total message length (incl '+' and '$', '#' and checksum bytes */ cmp r4, #MSG_SEGMENTSIZE /* If total message length > MSG_SEGMENTSIZE */ mov r1, #0 /* Initialize Segment Number = 0 (last segment) first */ mov r0, #0 /* Initial cummulative message length */ mov r5, #0 /* Previous cummulative message length */ /* We assume unsigned message lengths, so the arithmetic MUST NOT result in negative values */ _cont_putMsg: cmp r4, r0 movls r0, #0 /* R0: Exit status (success) */ bls exit_dbg__putDebugMsg /* If Total message length (r4) <= Cummulative message length (r0), we're done */ add r2, r0, #MSG_SEGMENTSIZE /* R2: calculate new Max cummulative message length */ cmp r4, r2 /* Check total message length (R4) against new Max cummulative message length (R2) */ subls r0, r4, r0 /* if total message length (R4) <= new Max cummulative message length (R2), send remainder */ movls r1, #0 /* Flag as last segment (Segment Number = 0) */ movhi r0, #MSG_SEGMENTSIZE /* else send MSG_SEGMENTSIZE bytes */ addhi r1, r1, #1 /* Increment Segment Number */ cmp r1, #MSG_NUMSEGMENTS bhs exit_dbg__putMsgError /* If Segment Number >= MSG_NUMSEGMENTS, flag error */ bl _copy_msg_to_commbuf /* R0: cummulative message length, R1: segment number */ teq r5, r0 /* Check if we managed to transmit the previous message */ beq exit_dbg__putMsgError /* No, flag error */ movne r5, r0 /* Update previous cummulative message length */ b _cont_putMsg exit_dbg__putMsgError: mov r0, #MSGBUF_MSGERROR exit_dbg__putDebugMsg: ldmfd sp!, {r4,r5,pc} .global dbg__sendAckOrNak /* dbg__sendAckOrNak * Send Ack (for successful receipt of message) * or Nak (for Retransmission due to received message Checksum error) (Blocking) . * On entry: * No parameters (assume pointers were initialized previously using dbg__comm_init) * On exit: * r0: status (0: success, -1: error) * r1: destroyed * Note: An Ack Or Nak is indicated by '+' or '-', which is prepended with the Comm header and sent (without checksum) * Sending Ack is only done for Continue and Step commands, where GDB does not expect any replies. */ dbg__sendAckOrNak: stmfd sp!, {lr} ldr r1, =debug_msgTxBufPtr /* R2: data structure base pointer */ ldr r0, [r1] /* Tx buffer Start Address */ str r0, [r1, #TXAPPENDPTR_OFFSET] /* Reset Tx buffer Append Pointer */ mov r1, #0 /* Initialize Segment Number = 0 (last segment) */ mov r0, #1 /* Retransmission message length = 1 */ bl _copy_msg_to_commbuf /* R0: cummulative message length, R1: segment number */ cmp r0, #1 /* Check if we managed to transmit the previous message */ moveq r0, #0 /* R0: Exit status (success) */ movne r0, #MSGBUF_MSGERROR /* R0: Exit status (error) */ ldmfd sp!, {pc}