/* MN10300 On-chip serial driver for gdbstub I/O * * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public Licence * as published by the Free Software Foundation; either version * 2 of the Licence, or (at your option) any later version. */ #include <linux/string.h> #include <linux/kernel.h> #include <linux/signal.h> #include <linux/sched.h> #include <linux/mm.h> #include <linux/console.h> #include <linux/init.h> #include <linux/tty.h> #include <asm/pgtable.h> #include <asm/system.h> #include <asm/gdb-stub.h> #include <asm/exceptions.h> #include <unit/clock.h> #include "mn10300-serial.h" #if defined(CONFIG_GDBSTUB_ON_TTYSM0) struct mn10300_serial_port *const gdbstub_port = &mn10300_serial_port_sif0; #elif defined(CONFIG_GDBSTUB_ON_TTYSM1) struct mn10300_serial_port *const gdbstub_port = &mn10300_serial_port_sif1; #else struct mn10300_serial_port *const gdbstub_port = &mn10300_serial_port_sif2; #endif /* * initialise the GDB stub I/O routines */ void __init gdbstub_io_init(void) { uint16_t scxctr; int tmp; switch (gdbstub_port->clock_src) { case MNSCx_CLOCK_SRC_IOCLK: gdbstub_port->ioclk = MN10300_IOCLK; break; #ifdef MN10300_IOBCLK case MNSCx_CLOCK_SRC_IOBCLK: gdbstub_port->ioclk = MN10300_IOBCLK; break; #endif default: BUG(); } /* set up the serial port */ gdbstub_io_set_baud(115200); /* we want to get serial receive interrupts */ set_intr_level(gdbstub_port->rx_irq, GxICR_LEVEL_0); set_intr_level(gdbstub_port->tx_irq, GxICR_LEVEL_0); set_intr_stub(EXCEP_IRQ_LEVEL0, gdbstub_io_rx_handler); *gdbstub_port->rx_icr |= GxICR_ENABLE; tmp = *gdbstub_port->rx_icr; /* enable the device */ scxctr = SC01CTR_CLN_8BIT; /* 1N8 */ switch (gdbstub_port->div_timer) { case MNSCx_DIV_TIMER_16BIT: scxctr |= SC0CTR_CK_TM8UFLOW_8; /* == SC1CTR_CK_TM9UFLOW_8 == SC2CTR_CK_TM10UFLOW_8 */ break; case MNSCx_DIV_TIMER_8BIT: scxctr |= SC0CTR_CK_TM2UFLOW_8; break; } scxctr |= SC01CTR_TXE | SC01CTR_RXE; *gdbstub_port->_control = scxctr; tmp = *gdbstub_port->_control; /* permit level 0 IRQs only */ asm volatile( " and %0,epsw \n" " or %1,epsw \n" : : "i"(~EPSW_IM), "i"(EPSW_IE|EPSW_IM_1) ); } /* * set up the GDB stub serial port baud rate timers */ void gdbstub_io_set_baud(unsigned baud) { const unsigned bits = 10; /* 1 [start] + 8 [data] + 0 [parity] + * 1 [stop] */ unsigned long ioclk = gdbstub_port->ioclk; unsigned xdiv, tmp; uint16_t tmxbr; uint8_t tmxmd; if (!baud) { baud = 9600; } else if (baud == 134) { baud = 269; /* 134 is really 134.5 */ xdiv = 2; } try_alternative: xdiv = 1; switch (gdbstub_port->div_timer) { case MNSCx_DIV_TIMER_16BIT: tmxmd = TM8MD_SRC_IOCLK; tmxbr = tmp = (ioclk / (baud * xdiv) + 4) / 8 - 1; if (tmp > 0 && tmp <= 65535) goto timer_okay; tmxmd = TM8MD_SRC_IOCLK_8; tmxbr = tmp = (ioclk / (baud * 8 * xdiv) + 4) / 8 - 1; if (tmp > 0 && tmp <= 65535) goto timer_okay; tmxmd = TM8MD_SRC_IOCLK_32; tmxbr = tmp = (ioclk / (baud * 32 * xdiv) + 4) / 8 - 1; if (tmp > 0 && tmp <= 65535) goto timer_okay; break; case MNSCx_DIV_TIMER_8BIT: tmxmd = TM2MD_SRC_IOCLK; tmxbr = tmp = (ioclk / (baud * xdiv) + 4) / 8 - 1; if (tmp > 0 && tmp <= 255) goto timer_okay; tmxmd = TM2MD_SRC_IOCLK_8; tmxbr = tmp = (ioclk / (baud * 8 * xdiv) + 4) / 8 - 1; if (tmp > 0 && tmp <= 255) goto timer_okay; tmxmd = TM2MD_SRC_IOCLK_32; tmxbr = tmp = (ioclk / (baud * 32 * xdiv) + 4) / 8 - 1; if (tmp > 0 && tmp <= 255) goto timer_okay; break; } /* as a last resort, if the quotient is zero, default to 9600 bps */ baud = 9600; goto try_alternative; timer_okay: gdbstub_port->uart.timeout = (2 * bits * HZ) / baud; gdbstub_port->uart.timeout += HZ / 50; /* set the timer to produce the required baud rate */ switch (gdbstub_port->div_timer) { case MNSCx_DIV_TIMER_16BIT: *gdbstub_port->_tmxmd = 0; *gdbstub_port->_tmxbr = tmxbr; *gdbstub_port->_tmxmd = TM8MD_INIT_COUNTER; *gdbstub_port->_tmxmd = tmxmd | TM8MD_COUNT_ENABLE; break; case MNSCx_DIV_TIMER_8BIT: *gdbstub_port->_tmxmd = 0; *(volatile u8 *) gdbstub_port->_tmxbr = (u8)tmxbr; *gdbstub_port->_tmxmd = TM2MD_INIT_COUNTER; *gdbstub_port->_tmxmd = tmxmd | TM2MD_COUNT_ENABLE; break; } } /* * wait for a character to come from the debugger */ int gdbstub_io_rx_char(unsigned char *_ch, int nonblock) { unsigned ix; u8 ch, st; *_ch = 0xff; if (gdbstub_rx_unget) { *_ch = gdbstub_rx_unget; gdbstub_rx_unget = 0; return 0; } try_again: /* pull chars out of the buffer */ ix = gdbstub_rx_outp; barrier(); if (ix == gdbstub_rx_inp) { if (nonblock) return -EAGAIN; #ifdef CONFIG_MN10300_WD_TIMER watchdog_alert_counter = 0; #endif /* CONFIG_MN10300_WD_TIMER */ goto try_again; } ch = gdbstub_rx_buffer[ix++]; st = gdbstub_rx_buffer[ix++]; barrier(); gdbstub_rx_outp = ix & (PAGE_SIZE - 1); st &= SC01STR_RXF | SC01STR_RBF | SC01STR_FEF | SC01STR_PEF | SC01STR_OEF; /* deal with what we've got * - note that the UART doesn't do BREAK-detection for us */ if (st & SC01STR_FEF && ch == 0) { switch (gdbstub_port->rx_brk) { case 0: gdbstub_port->rx_brk = 1; goto try_again; case 1: gdbstub_port->rx_brk = 2; goto try_again; case 2: gdbstub_port->rx_brk = 3; gdbstub_proto("### GDB MNSERIAL Rx Break Detected" " ###\n"); return -EINTR; default: goto try_again; } } else if (st & SC01STR_FEF) { if (gdbstub_port->rx_brk) goto try_again; gdbstub_proto("### GDB MNSERIAL Framing Error ###\n"); return -EIO; } else if (st & SC01STR_OEF) { if (gdbstub_port->rx_brk) goto try_again; gdbstub_proto("### GDB MNSERIAL Overrun Error ###\n"); return -EIO; } else if (st & SC01STR_PEF) { if (gdbstub_port->rx_brk) goto try_again; gdbstub_proto("### GDB MNSERIAL Parity Error ###\n"); return -EIO; } else { /* look for the tail-end char on a break run */ if (gdbstub_port->rx_brk == 3) { switch (ch) { case 0xFF: case 0xFE: case 0xFC: case 0xF8: case 0xF0: case 0xE0: case 0xC0: case 0x80: case 0x00: gdbstub_port->rx_brk = 0; goto try_again; default: break; } } gdbstub_port->rx_brk = 0; gdbstub_io("### GDB Rx %02x (st=%02x) ###\n", ch, st); *_ch = ch & 0x7f; return 0; } } /* * send a character to the debugger */ void gdbstub_io_tx_char(unsigned char ch) { while (*gdbstub_port->_status & SC01STR_TBF) continue; if (ch == 0x0a) { *(u8 *) gdbstub_port->_txb = 0x0d; while (*gdbstub_port->_status & SC01STR_TBF) continue; } *(u8 *) gdbstub_port->_txb = ch; } /* * flush the transmission buffers */ void gdbstub_io_tx_flush(void) { while (*gdbstub_port->_status & (SC01STR_TBF | SC01STR_TXF)) continue; }