summaryrefslogtreecommitdiff
path: root/arch/arm/common
diff options
context:
space:
mode:
authorRobert Walker <robert.walker@arm.com>2009-03-10 10:24:57 +0000
committerCatalin Marinas <catalin.marinas@arm.com>2009-03-10 10:24:57 +0000
commitfd745777eec63e2c098b9f4cb60f5c94e0b9b298 (patch)
tree68cf7ef04937161b6f1a7e3128a660159fc8db7a /arch/arm/common
parentd47913c8fb9ff68ab16dfedcba0c48db8d7800c7 (diff)
Add RealView-ICE DCC ethernet and console driver
This patch adds support for console and Ethernet over DCC using RealView-ICE to Linux 2.6.28-arm1. Signed-off-by: Robert Walker <robert.walker@arm.com> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Diffstat (limited to 'arch/arm/common')
-rw-r--r--arch/arm/common/Makefile2
-rw-r--r--arch/arm/common/rvidcc_common.c794
-rw-r--r--arch/arm/common/rvidcc_config.h166
-rw-r--r--arch/arm/common/rvidcc_interface.h182
-rw-r--r--arch/arm/common/rvidcc_linux.c1014
5 files changed, 2158 insertions, 0 deletions
diff --git a/arch/arm/common/Makefile b/arch/arm/common/Makefile
index 325e4b6a6afb..e6bdb9657dee 100644
--- a/arch/arm/common/Makefile
+++ b/arch/arm/common/Makefile
@@ -17,3 +17,5 @@ obj-$(CONFIG_SHARP_SCOOP) += scoop.o
obj-$(CONFIG_ARCH_IXP2000) += uengine.o
obj-$(CONFIG_ARCH_IXP23XX) += uengine.o
obj-$(CONFIG_PCI_HOST_ITE8152) += it8152.o
+obj-$(CONFIG_DEBUG_RVIDCC) += rvidcc.o
+rvidcc-objs := rvidcc_common.o rvidcc_linux.o
diff --git a/arch/arm/common/rvidcc_common.c b/arch/arm/common/rvidcc_common.c
new file mode 100644
index 000000000000..3e21709095d0
--- /dev/null
+++ b/arch/arm/common/rvidcc_common.c
@@ -0,0 +1,794 @@
+/*
+ Copyright (C) 2004-2007 ARM Limited.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ As a special exception, if other files instantiate templates or use macros
+ or inline functions from this file, or you compile this file and link it
+ with other works to produce a work based on this file, this file does not
+ by itself cause the resulting work to be covered by the GNU General Public
+ License. However the source code for this file must still be made available
+ in accordance with section (3) of the GNU General Public License.
+
+ This exception does not invalidate any other reasons why a work based on
+ this file might be covered by the GNU General Public License.
+*/
+
+/* ================================================================
+ Universal RealView ICE DCC communications driver - How to use it
+ ================================================================
+
+ This driver is split into 2 sections: a generic section (this file) that
+ handles the DCC hardware access and buffering and an OS-dependent file that
+ integrates the driver into the OS network and TTY layers. The interface
+ between the sections is defined in rvidcc_interface.h, which contains
+ prototypes and documentation for the functions in the generic section that
+ can be called from the OS-dependent section and the callback functions
+ called from the generic section that must be provided by the OS-dependent
+ section.
+
+ This driver is designed to be able to work with or without interrupts, to
+ use raw DCC comms (as a TTY) or virtual Ethernet. The driver is configured
+ in the rvidcc_config.h header, including options to select which cores to
+ support, whether to support ethernet, buffer sizes and how to
+ enable/disable interrupts. See the example configs for details of all
+ options.
+
+ All buffers used by the driver are in the system memory space. All
+ pointer arguments for functions called by or from the generic section are
+ pointers to system memory. It is the responsibility of the OS-dependent
+ section to copy to/from user memory.
+
+ ARM Ltd. highly recommends using interrupt-driven DCC communications, and
+ to use the virtual Ethernet system, even if only a TTY is required.
+
+ Raw DCC communications transfers data in 32-bit words. Thus if virtual
+ Ethernet is not used, data transfers will contain null padding for blocks
+ of data that do not have a multiple of 4 bytes. For most command-line
+ applications, this is unacceptable. However protocols, such as the remote
+ gdb protocol, will tolerate the extra nulls, provided they occur between
+ packets. This, of course, requires that data be passed to the driver as
+ packets, and not as byte-by-byte as some TTY drivers are in the habit of
+ doing.
+
+ Using the virtual Ethernet system does not force you to have to support
+ networking, or an IP stack, etc. The virtual Ethernet system contains a
+ TTY channel and an Ethernet channel, and if only the TTY channel is
+ required, the Ethernet channel and its data can be ignored. Using this
+ for TTY only has the advantage that data packets do not have to be a
+ multiple of 4 bytes, and so it is suitable for command-line consoles.
+
+ The virtual Ethernet system is not true Ethernet in that it can only carry
+ IP traffic. It does, however, have a mechanism for obtaining a unique MAC
+ address from the ICE, and the target will receive ARP-style queries to
+ determine if the target has a particular IP address. Thus DHCP can be used
+ to obtain an IP address from an external DHCP server, without the need for
+ any special configuration of the ICE.
+*/
+
+#include "rvidcc_config.h"
+#include "rvidcc_interface.h"
+
+
+#ifdef __ARMCC_VERSION
+#define INLINE __inline
+#else
+#define INLINE inline
+#endif
+
+
+#ifndef RVIDCC_RAW
+
+#define UNCOMP_TTY_START 0xa5580000
+#define UNCOMP_ETH_START 0xa55a0000
+#define START_MASK 0xfffc0000
+#define START_SENTINEL UNCOMP_TTY_START
+
+#endif /* RVIDCC_RAW */
+
+#if !defined(RVIDCC_ARM79) && !defined(RVIDCC_ARM10) && !defined(RVIDCC_ARM11)
+#warning No cores are enabled. Define at least one of RVIDCC_ARM79, RVIDCC_ARM10 or RVIDCC_ARM11
+#endif
+
+
+#define DCC_ARM9_RBIT (1 << 0)
+#define DCC_ARM9_WBIT (1 << 1)
+#define DCC_ARM10_RBIT (1 << 7)
+#define DCC_ARM10_WBIT (1 << 6)
+#define DCC_ARM11_RBIT (1 << 30)
+#define DCC_ARM11_WBIT (1 << 29)
+
+/* Access primitives: x must be unsigned int */
+
+#ifdef __ARMCC_VERSION
+
+/* RVCT versions */
+#define READ_CORE_ID(x) { __asm { mrc p15, 0, x, c0, c0, 0 } \
+ x = (x >> 4) & 0xFFF; }
+
+#ifdef RVIDCC_ARM79
+#define WRITE_ARM9_DCC(x) __asm { mcr p14, 0, x, c1, c0, 0 }
+#define READ_ARM9_DCC(x) __asm { mrc p14, 0, x, c1, c0, 0 }
+#define STATUS_ARM9_DCC(x) __asm { mrc p14, 0, x, c0, c0, 0 }
+#define CAN_READ_ARM9_DCC(x) {STATUS_ARM9_DCC(x); x &= DCC_ARM9_RBIT;}
+#define CAN_WRITE_ARM9_DCC(x) {STATUS_ARM9_DCC(x); x &= DCC_ARM9_WBIT; x = (x==0);}
+#endif
+
+#ifdef RVIDCC_ARM10
+#define WRITE_ARM10_DCC(x) __asm { mcr p14, 0, x, c0, c5, 0 }
+#define READ_ARM10_DCC(x) __asm { mrc p14, 0, x, c0, c5, 0 }
+#define STATUS_ARM10_DCC(x) __asm { mrc p14, 0, x, c0, c1, 0 }
+#define CAN_READ_ARM10_DCC(x) {STATUS_ARM10_DCC(x); x &= DCC_ARM10_RBIT;}
+#define CAN_WRITE_ARM10_DCC(x) {STATUS_ARM10_DCC(x); x &= DCC_ARM10_WBIT; x = (x==0);}
+#endif
+
+#ifdef RVIDCC_ARM11
+#define WRITE_ARM11_DCC(x) __asm { mcr p14, 0, x, c0, c5, 0 }
+#define READ_ARM11_DCC(x) __asm { mrc p14, 0, x, c0, c5, 0 }
+#define STATUS_ARM11_DCC(x) __asm { mrc p14, 0, x, c0, c1, 0 }
+#define CAN_READ_ARM11_DCC(x) {STATUS_ARM11_DCC(x); x &= DCC_ARM11_RBIT;}
+#define CAN_WRITE_ARM11_DCC(x) {STATUS_ARM11_DCC(x); x &= DCC_ARM11_WBIT; x = (x==0);}
+#endif
+
+#else
+
+/* GCC versions */
+#define READ_CORE_ID(x) { __asm__ ("mrc p15, 0, %0, c0, c0, 0\n" : "=r" (x)); \
+ x = (x >> 4) & 0xFFF; }
+
+#ifdef RVIDCC_ARM79
+#define WRITE_ARM9_DCC(x) __asm__ volatile ("mcr p14, 0, %0, c1, c0, 0\n" : : "r" (x))
+#define READ_ARM9_DCC(x) __asm__ volatile ("mrc p14, 0, %0, c1, c0, 0\n" : "=r" (x))
+#define STATUS_ARM9_DCC(x) __asm__ volatile ("mrc p14, 0, %0, c0, c0, 0\n" : "=r" (x))
+#define CAN_READ_ARM9_DCC(x) {STATUS_ARM9_DCC(x); x &= DCC_ARM9_RBIT;}
+#define CAN_WRITE_ARM9_DCC(x) {STATUS_ARM9_DCC(x); x &= DCC_ARM9_WBIT; x = (x==0);}
+#endif
+
+#ifdef RVIDCC_ARM10
+#define WRITE_ARM10_DCC(x) __asm__ volatile ("mcr p14, 0, %0, c0, c5, 0\n" : : "r" (x))
+#define READ_ARM10_DCC(x) __asm__ volatile ("mrc p14, 0, %0, c0, c5, 0\n" : "=r" (x))
+#define STATUS_ARM10_DCC(x) __asm__ volatile ("mrc p14, 0, %0, c0, c1, 0\n" : "=r" (x))
+#define CAN_READ_ARM10_DCC(x) {STATUS_ARM10_DCC(x); x &= DCC_ARM10_RBIT;}
+#define CAN_WRITE_ARM10_DCC(x) {STATUS_ARM10_DCC(x); x &= DCC_ARM10_WBIT; x = (x==0);}
+#endif
+
+#ifdef RVIDCC_ARM11
+#define WRITE_ARM11_DCC(x) __asm__ volatile ("mcr p14, 0, %0, c0, c5, 0\n" : : "r" (x))
+#define READ_ARM11_DCC(x) __asm__ volatile ("mrc p14, 0, %0, c0, c5, 0\n" : "=r" (x))
+#define STATUS_ARM11_DCC(x) __asm__ volatile ("mrc p14, 0, %0, c0, c1, 0\n" : "=r" (x))
+#define CAN_READ_ARM11_DCC(x) {STATUS_ARM11_DCC(x); x &= DCC_ARM11_RBIT;}
+#define CAN_WRITE_ARM11_DCC(x) {STATUS_ARM11_DCC(x); x &= DCC_ARM11_WBIT; x = (x==0);}
+#endif
+
+#endif
+
+static enum {arm9_and_earlier, arm10, arm11_and_later} arm_type = arm9_and_earlier;
+
+/* Prototypes */
+static void rvidcc_write_lowlevel(const void *ptr, size_t len);
+
+/* Queues */
+
+struct ringbuffer
+{
+ unsigned char *buf;
+ size_t size;
+ unsigned char* readPtr;
+ unsigned char* writePtr;
+};
+
+static unsigned char dcc_inbuffer[RVIDCC_BUFSIZE];
+static struct ringbuffer dcc_inbuf;
+static unsigned char dcc_outbuffer[RVIDCC_BUFSIZE];
+static struct ringbuffer dcc_outbuf;
+
+#ifndef RVIDCC_RAW
+#define DCC_TTY_INBUF_SIZE (RVIDCC_BUFSIZE/2)
+static unsigned char dcc_temp_source[DCC_TTY_INBUF_SIZE];
+static unsigned char dcc_tty_inbuffer[DCC_TTY_INBUF_SIZE];
+static struct ringbuffer dcc_tty_inbuf;
+#define DCC_TTY_INBUF dcc_tty_inbuf
+
+#else
+
+#define DCC_TTY_INBUF dcc_inbuf
+
+#endif
+
+
+/* This ringbuffer has no locks as it assumes only 1 reader and 1 writer and
+ * assumes a 32-bit access is atomic */
+INLINE static void ringbuf_advance_read(struct ringbuffer* ring_buf, size_t amount)
+{
+ unsigned char* temptr = ring_buf->readPtr + amount;
+ while (temptr >= ring_buf->buf + ring_buf->size)
+ temptr -= ring_buf->size;
+ ring_buf->readPtr = temptr;
+}
+
+
+INLINE static void ringbuf_advance_write(struct ringbuffer* ring_buf, size_t amount)
+{
+ unsigned char* temptr = ring_buf->writePtr + amount;
+ while (temptr >= ring_buf->buf + ring_buf->size)
+ temptr -= ring_buf->size;
+ ring_buf->writePtr = temptr;
+}
+
+
+INLINE static void ringbuf_get(struct ringbuffer* ring_buf,
+ unsigned char* dst, size_t amount)
+{
+ unsigned char* source = ring_buf->readPtr;
+ size_t len_to_end = ring_buf->buf + ring_buf->size - source;
+ if (amount > len_to_end)
+ {
+ memcpy(dst, source, len_to_end);
+ memcpy(dst + len_to_end, ring_buf->buf, (amount - len_to_end));
+ }
+ else
+ memcpy(dst, source, amount);
+}
+
+
+INLINE static void ringbuf_put(struct ringbuffer* ring_buf,
+ unsigned char* src, size_t amount)
+{
+ unsigned char* dest = ring_buf->writePtr;
+ unsigned int len_to_end = ring_buf->buf + ring_buf->size - dest;
+ if (amount > len_to_end)
+ {
+ memcpy(dest, src, len_to_end);
+ memcpy(ring_buf->buf, src + len_to_end, amount - len_to_end);
+ }
+ else
+ memcpy(dest, src, amount);
+}
+
+
+INLINE static size_t ringbuf_available_data(struct ringbuffer* ring_buf)
+{
+ int avail = ring_buf->writePtr - ring_buf->readPtr;
+
+ if (avail < 0)
+ /* handle wrap around */
+ avail += ring_buf->size;
+
+ return (size_t)avail;
+}
+
+
+INLINE static size_t ringbuf_available_space(struct ringbuffer* ring_buf)
+{
+ size_t avail;
+
+ if (ring_buf->writePtr >= ring_buf->readPtr)
+ avail = ring_buf->size - (ring_buf->writePtr - ring_buf->readPtr) - 1;
+ else
+ avail = ring_buf->readPtr - ring_buf->writePtr - 1;
+
+ return avail;
+}
+
+
+// conditionally swap endian of a 32bit word
+#ifdef RVIDCC_BIG_ENDIAN
+#define to_little_endian(x) ((((x)&0xff) << 24) | (((x)&0xff00) << 8) | (((x)&0xff0000) >> 8) | (((x)&0xff000000) >> 24))
+#else
+#define to_little_endian(x) (x)
+#endif
+
+
+int rvidcc_init(void)
+{
+ register unsigned int id;
+ int err = 0;
+#ifndef RVIDCC_RAW
+ unsigned int macRequest = to_little_endian(UNCOMP_ETH_START);
+#endif
+
+ RVIDCC_DISABLE_INTERRUPTS;
+
+ /* check core type, raising error if core not supported */
+ READ_CORE_ID(id);
+ if ((id & 0xF00) == 0xA00)
+ {
+ arm_type = arm10;
+#ifndef RVIDCC_ARM10
+ err = RVIDCC_ERR_CORE_NOT_SUPPORTED;
+#endif
+ }
+ else if (id >= 0xb00)
+ {
+ arm_type = arm11_and_later;
+#ifndef RVIDCC_ARM11
+ err = RVIDCC_ERR_CORE_NOT_SUPPORTED;
+#endif
+ }
+ else
+ {
+ arm_type = arm9_and_earlier;
+#ifndef RVIDCC_ARM79
+ err = RVIDCC_ERR_CORE_NOT_SUPPORTED;
+#endif
+ }
+
+ if (!err)
+ {
+ dcc_inbuf.buf = dcc_inbuffer;
+ dcc_inbuf.size = RVIDCC_BUFSIZE;
+ dcc_inbuf.readPtr = dcc_inbuf.buf;
+ dcc_inbuf.writePtr = dcc_inbuf.buf;
+
+ dcc_outbuf.buf = dcc_outbuffer;
+ dcc_outbuf.size = RVIDCC_BUFSIZE;
+ dcc_outbuf.readPtr = dcc_outbuf.buf;
+ dcc_outbuf.writePtr = dcc_outbuf.buf;
+
+#ifndef RVIDCC_RAW
+ dcc_tty_inbuf.buf = dcc_tty_inbuffer;
+ dcc_tty_inbuf.size = DCC_TTY_INBUF_SIZE;
+ dcc_tty_inbuf.readPtr = dcc_tty_inbuf.buf;
+ dcc_tty_inbuf.writePtr = dcc_tty_inbuf.buf;
+
+ /* request a MAC address */
+ rvidcc_write_lowlevel(&macRequest, sizeof(unsigned int));
+#endif
+
+ /* Enable interrupts (write interrupt will be enabled when something is written) */
+ RVIDCC_ENABLE_READ_INTERRUPT;
+ RVIDCC_ENABLE_INTERRUPTS;
+ }
+
+ return err;
+}
+
+
+void rvidcc_write(void)
+{
+ register unsigned int reg;
+ int pollcount = -1;
+ int some_transfer = 0;
+
+ switch (arm_type)
+ {
+#ifdef RVIDCC_ARM79
+ case arm9_and_earlier:
+ {
+ /* Try to write everything available */
+ while (ringbuf_available_data(&dcc_outbuf) >= sizeof(unsigned int))
+ {
+ /* On the first word, the write is aborted immediately if the DCC write
+ * register is full. On subsequent words, the driver waits for a
+ * bit (RVIDCC_MAX_INLINE_POLLS times) as the RVI will probably
+ * read the DCC register soon because it knows data is being sent. */
+ do
+ {
+ CAN_WRITE_ARM9_DCC(reg);
+ } while (!reg && pollcount != -1 && --pollcount > 0);
+
+ if (!reg)
+ break;
+
+ reg = to_little_endian(*(unsigned int *)dcc_outbuf.readPtr);
+ WRITE_ARM9_DCC(reg);
+ ringbuf_advance_read(&dcc_outbuf, sizeof(unsigned int));
+ pollcount = RVIDCC_MAX_INLINE_POLLS;
+ some_transfer = 1;
+ }
+
+ pollcount = -1;
+ break;
+ }
+#endif
+
+#ifdef RVIDCC_ARM10
+ case arm10:
+ {
+ /* Try to write everything available */
+ while (ringbuf_available_data(&dcc_outbuf) >= sizeof(unsigned int))
+ {
+ do
+ {
+ CAN_WRITE_ARM10_DCC(reg);
+ } while (!reg && pollcount != -1 && --pollcount > 0);
+
+ if (!reg)
+ break;
+
+ reg = to_little_endian(*(unsigned int *)dcc_outbuf.readPtr);
+ WRITE_ARM10_DCC(reg);
+ ringbuf_advance_read(&dcc_outbuf, sizeof(unsigned int));
+ pollcount = RVIDCC_MAX_INLINE_POLLS;
+ some_transfer = 1;
+ }
+
+ pollcount = -1;
+ break;
+ }
+#endif
+
+#ifdef RVIDCC_ARM11
+ case arm11_and_later:
+ {
+ /* Try to write everything available */
+ while (ringbuf_available_data(&dcc_outbuf) >= sizeof(unsigned int))
+ {
+ do
+ {
+ CAN_WRITE_ARM11_DCC(reg);
+ } while (!reg && pollcount != -1 && --pollcount > 0);
+
+ if (!reg)
+ break;
+
+ reg = to_little_endian(*(unsigned int *)dcc_outbuf.readPtr);
+ WRITE_ARM11_DCC(reg);
+ ringbuf_advance_read(&dcc_outbuf, sizeof(unsigned int));
+ pollcount = RVIDCC_MAX_INLINE_POLLS;
+ some_transfer = 1;
+ }
+
+ pollcount = -1;
+ break;
+ }
+#endif
+
+ default:
+ break;
+ }
+
+ if (some_transfer)
+ rvidcc_cb_notify();
+
+ /* Disable interrupt if nothing left to write */
+ if (ringbuf_available_data(&dcc_outbuf) == 0)
+ RVIDCC_DISABLE_WRITE_INTERRUPT;
+}
+
+
+/* this routine assumes it is non-interruptible by others that access the queue pointers */
+/* this is written out longhand because it may be called from an interrupt routine */
+void rvidcc_read(void)
+{
+ register unsigned int reg;
+ int pollcount = -1;
+ int some_transfer = 0;
+
+ switch (arm_type)
+ {
+#ifdef RVIDCC_ARM79
+ case arm9_and_earlier:
+ {
+ /* Try to read everything available */
+ while (ringbuf_available_space(&dcc_inbuf) >= sizeof(unsigned int))
+ {
+ do
+ {
+ CAN_READ_ARM9_DCC(reg);
+ } while (!reg && pollcount != -1 && --pollcount > 0);
+
+ if (!reg)
+ break;
+
+ READ_ARM9_DCC(reg);
+ *(unsigned int *)dcc_inbuf.writePtr = to_little_endian(reg);
+ ringbuf_advance_write(&dcc_inbuf, sizeof(unsigned int));
+ pollcount = RVIDCC_MAX_INLINE_POLLS;
+ some_transfer = 1;
+ }
+ break;
+ }
+#endif
+
+#ifdef RVIDCC_ARM10
+ case arm10:
+ {
+ /* Try to read everything available */
+ while (ringbuf_available_space(&dcc_inbuf) >= sizeof(unsigned int))
+ {
+ do
+ {
+ CAN_READ_ARM10_DCC(reg);
+ } while (!reg && pollcount != -1 && --pollcount > 0);
+
+ if (!reg)
+ break;
+
+ READ_ARM10_DCC(reg);
+ *(unsigned int *)dcc_inbuf.writePtr = to_little_endian(reg);
+ ringbuf_advance_write(&dcc_inbuf, sizeof(unsigned int));
+ pollcount = RVIDCC_MAX_INLINE_POLLS;
+ some_transfer = 1;
+ }
+ break;
+ }
+#endif
+
+#ifdef RVIDCC_ARM11
+ case arm11_and_later:
+ {
+ /* Try to read everything available */
+ while (ringbuf_available_space(&dcc_inbuf) >= sizeof(unsigned int))
+ {
+ do
+ {
+ CAN_READ_ARM11_DCC(reg);
+ } while (!reg && pollcount != -1 && --pollcount > 0);
+
+ if (!reg)
+ break;
+
+ READ_ARM11_DCC(reg);
+ *(unsigned int *)dcc_inbuf.writePtr = to_little_endian(reg);
+ ringbuf_advance_write(&dcc_inbuf, sizeof(unsigned int));
+ pollcount = RVIDCC_MAX_INLINE_POLLS;
+ some_transfer = 1;
+ }
+ break;
+ }
+#endif
+
+ default:
+ break;
+ }
+
+ if (ringbuf_available_space(&dcc_inbuf) == 0)
+ RVIDCC_DISABLE_READ_INTERRUPT;
+
+ if (some_transfer)
+ rvidcc_cb_notify();
+}
+
+
+/* this routine assumes it is not interrupted by others that access the queue pointers */
+void rvidcc_poll(void)
+{
+ rvidcc_write();
+ rvidcc_read();
+}
+
+
+#ifndef RVIDCC_RAW
+void rvidcc_process(void)
+{
+ unsigned int sentinel = 0;
+ int compsize = 0;
+
+ /* Process received data */
+ while (ringbuf_available_data(&dcc_inbuf) >= sizeof(unsigned int))
+ {
+ int bytes;
+ unsigned char *buf;
+
+ /* Search for start sentinel */
+ sentinel = to_little_endian(*(unsigned int *)dcc_inbuf.readPtr);
+ compsize = sentinel & 0xffff;
+
+ /* Discard if not a start sentinel */
+ if ((sentinel & START_MASK) != START_SENTINEL ||
+ compsize > sizeof(dcc_temp_source) || compsize <= 0)
+ {
+ ringbuf_advance_read(&dcc_inbuf, sizeof(unsigned int));
+ continue;
+ }
+
+ /* Sentinel found, come back later if not enough data */
+ bytes = ringbuf_available_data(&dcc_inbuf);
+ if (bytes < compsize + sizeof(unsigned int))
+ break;
+
+ /* Copy to a contiguous buffer */
+ ringbuf_get(&dcc_inbuf,
+ dcc_temp_source, compsize + sizeof(unsigned int));
+
+ sentinel &= 0xffff0000;
+ buf = dcc_temp_source + sizeof(unsigned int);
+
+ bytes = compsize;
+
+ /* Round up to nearest whole word */
+ if (compsize & 0x3)
+ compsize = (compsize + 4) & ~0x3;
+
+ /* advance by packet size + sentinel */
+ ringbuf_advance_read(&dcc_inbuf, compsize + sizeof(unsigned int));
+
+ /* allow more data to be received */
+ RVIDCC_ENABLE_READ_INTERRUPT;
+
+ /* Process TTY or IP packet accordingly */
+ if (sentinel == UNCOMP_TTY_START)
+ {
+ ringbuf_put(&dcc_tty_inbuf, buf, bytes);
+ ringbuf_advance_write(&dcc_tty_inbuf, bytes);
+ }
+ else
+ {
+ if (bytes == 4) /* ARP 'is this IP address you?' */
+ {
+ if (rvidcc_cb_has_addr(buf))
+ rvidcc_transmit_ip_packet(buf, 4);
+ }
+ else if (bytes == 6) /* set MAC address */
+ rvidcc_cb_set_mac_address(buf);
+ else
+ rvidcc_cb_ip_packet_received(buf, (size_t)bytes);
+ }
+ }
+}
+#endif
+
+
+#ifdef RVIDCC_SCAN_INPUT_FOR
+int rvidcc_scan_input_for(char c, size_t span)
+{
+ int ret = 0;
+
+ size_t bytesAvail = ringbuf_available_data(&DCC_TTY_INBUF);
+ if (bytesAvail && span)
+ {
+ unsigned char *p = DCC_TTY_INBUF.readPtr;
+
+ if (span > bytesAvail)
+ span = bytesAvail;
+
+ while (span--)
+ {
+ if (*p++ == c)
+ {
+ ret = 1;
+ break;
+ }
+
+ if (p >= DCC_TTY_INBUF.buf + DCC_TTY_INBUF.size)
+ p = DCC_TTY_INBUF.buf;
+ }
+ }
+ return ret;
+}
+#endif
+
+
+#ifdef RVIDCC_OUTBUF_DATA
+size_t rvidcc_outbuf_data(void)
+{
+ return ringbuf_available_data(&dcc_outbuf);
+}
+#endif
+
+
+size_t rvidcc_serial_can_read(void)
+{
+ return ringbuf_available_data(&DCC_TTY_INBUF);
+}
+
+
+size_t rvidcc_serial_can_write(void)
+{
+ size_t space = ringbuf_available_space(&dcc_outbuf);
+
+ // protocol only has 16 bits for packet size, so limit packets to this size
+ if (space > (65536 - 4))
+ space = (65536 - 4);
+
+ return space;
+}
+
+
+size_t rvidcc_read_serial(void *ptr, size_t len)
+{
+ size_t bytes = ringbuf_available_data(&DCC_TTY_INBUF);
+ if (bytes && len)
+ {
+ if (bytes > len)
+ bytes = len;
+
+ ringbuf_get(&DCC_TTY_INBUF, (unsigned char*)ptr, bytes);
+#ifdef RVIDCC_RAW
+ /* round up to 4 bytes */
+ if (bytes & 0x3)
+ ringbuf_advance_read(&DCC_TTY_INBUF, ((bytes + 4) & ~0x3));
+ else
+ ringbuf_advance_read(&DCC_TTY_INBUF, bytes);
+
+ RVIDCC_ENABLE_READ_INTERRUPT;
+#else
+ ringbuf_advance_read(&DCC_TTY_INBUF, bytes);
+#endif
+ }
+ else
+ bytes = 0;
+
+ return bytes;
+}
+
+
+static void rvidcc_write_lowlevel(const void *ptr, size_t len)
+{
+ size_t rem;
+
+ ringbuf_put(&dcc_outbuf, (unsigned char*)ptr, len);
+
+ ringbuf_advance_write(&dcc_outbuf, len & ~0x3); // advance whole words
+
+ /* ensure excess bytes up to word boundary are null */
+ rem = (len & 0x3);
+ if (rem)
+ {
+ for (; rem < 4; rem++)
+ *(dcc_outbuf.writePtr + rem) = '\0';
+
+ ringbuf_advance_write(&dcc_outbuf, sizeof(unsigned long)); // advance remainder word
+ }
+
+
+ RVIDCC_ENABLE_WRITE_INTERRUPT;
+}
+
+
+size_t rvidcc_write_serial(const void *ptr, size_t len)
+{
+ size_t bytes;
+#ifndef RVIDCC_RAW
+ unsigned long sentinel;
+#endif
+
+ bytes = rvidcc_serial_can_write();
+
+#ifndef RVIDCC_RAW
+ if (bytes > sizeof(unsigned long))
+ {
+ /* Size limit the amount of data to be written, allowing for sentinel */
+ if (len > (bytes - sizeof(unsigned long)))
+ len = (bytes - sizeof(unsigned long));
+
+ sentinel = to_little_endian(UNCOMP_TTY_START | (unsigned short)len);
+ rvidcc_write_lowlevel(&sentinel, sizeof(unsigned long));
+ rvidcc_write_lowlevel(ptr, len);
+ }
+#else
+ if (bytes > 0)
+ {
+ /* Size limit the amount of data to be written */
+ if (len > bytes)
+ len = bytes;
+
+ rvidcc_write_lowlevel(ptr, len);
+ }
+#endif
+ else
+ len = 0;
+
+ return len;
+}
+
+
+#ifndef RVIDCC_RAW
+int rvidcc_transmit_ip_packet(void *packet, size_t length)
+{
+ if (rvidcc_serial_can_write() >= (length + 4))
+ {
+ unsigned long sentinel = to_little_endian(UNCOMP_ETH_START | (unsigned short)length);
+ rvidcc_write_lowlevel(&sentinel, 4);
+ rvidcc_write_lowlevel(packet, length);
+ }
+ else
+ length = 0;
+
+ return length;
+}
+#endif
+
+/* End of file */
diff --git a/arch/arm/common/rvidcc_config.h b/arch/arm/common/rvidcc_config.h
new file mode 100644
index 000000000000..faabe1db1b59
--- /dev/null
+++ b/arch/arm/common/rvidcc_config.h
@@ -0,0 +1,166 @@
+/*
+ Copyright (C) 2004-2007 ARM Limited.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ As a special exception, if other files instantiate templates or use macros
+ or inline functions from this file, or you compile this file and link it
+ with other works to produce a work based on this file, this file does not
+ by itself cause the resulting work to be covered by the GNU General Public
+ License. However the source code for this file must still be made available
+ in accordance with section (3) of the GNU General Public License.
+
+ This exception does not invalidate any other reasons why a work based on
+ this file might be covered by the GNU General Public License.
+*/
+
+#ifndef RVIDCC_CONFIG_H
+#define RVIDCC_CONFIG_H
+
+/*
+ *
+ * Platform headers that define basic types (e.g. size_t)
+ * ------------------------------------------------------
+ *
+ */
+#include <linux/types.h>
+#include <linux/netdevice.h>
+
+
+/*
+ * Put any platform specific definitions required by the interrupt macros here
+ * ---------------------------------------------------------------------------
+ */
+
+/* Structure containing our internal state, etc. */
+struct dccconfig
+{
+ unsigned int rx_interrupt;
+ unsigned int tx_interrupt;
+ unsigned long timer_poll_period;
+ int use_timer_poll;
+
+ volatile unsigned long irq_disable;
+};
+
+extern struct dccconfig dcc_config;
+
+
+/*
+ * Configuration
+ * -------------
+ */
+
+/*
+ * Define which ARM cores this binary is to be used on
+ * - RVIDCC_ARM79 for all ARM 7 and ARM 9 based cores
+ * - RVIDCC_ARM10 for all ARM 10 cores
+ * - RVIDCC_ARM11 for all ARM 11 and later cores (including Cortex)
+ */
+#define RVIDCC_ARM79
+#define RVIDCC_ARM10
+#define RVIDCC_ARM11
+
+/*
+ * Define RVIDCC_OUTBUF_DATA if the OS needs to check if all pending data
+ * has been sent out to the ICE.
+ */
+#define RVIDCC_OUTBUF_DATA
+
+/*
+ * Define RVIDCC_RAW if the virtual ethernet functionality is not required. See
+ * rvidcc.c for more information.
+ */
+#ifdef CONFIG_DEBUG_DCC_RAW
+#define RVIDCC_RAW
+#endif
+
+/*
+ * Define RVIDCC_BIG_ENDIAN if your platform is big endian.
+ */
+#ifdef CONFIG_CPU_BIG_ENDIAN
+#define RVIDCC_BIG_ENDIAN
+#endif
+
+/*
+ * Define RVIDCC_SCAN_INPUT_FOR if the OS needs to peek ahead in the TTY data
+ */
+#ifdef CONFIG_DEBUG_DCC_KGDB
+#define RVIDCC_SCAN_INPUT_FOR
+#endif
+
+/*
+ * RVIDCC_BUFSIZE defines the size of the input and output buffers for DCC data
+ */
+#ifndef RVIDCC_BUFSIZE
+#ifdef RVIDCC_RAW
+#define RVIDCC_BUFSIZE 4096
+#else
+#define RVIDCC_BUFSIZE 8192
+#endif
+#endif
+
+/*
+ * RVIDCC_MAX_INLINE_POLLS defines the number of times to the driver should
+ * check if the host is ready for more data in rvidcc_write or if the host has
+ * sent more data in rvidcc_read. Increasing this value may improve the data
+ * transfer rate, but will increase the length of time spent in the interrupt
+ * handler. Adjust this value according to the performance and latency
+ * requirements of the system.
+ */
+#define RVIDCC_MAX_INLINE_POLLS 500
+
+/*
+ * Interrupt configuration
+ *
+ * Define macros here to enable and disable interrupts. If the OS uses
+ * polling instead of interrupts, define as empty.
+ *
+ */
+
+#define RVIDCC_ENABLE_INTERRUPTS
+
+#define RVIDCC_DISABLE_INTERRUPTS
+
+/* Interrupt control macros */
+#define RVIDCC_ENABLE_WRITE_INTERRUPT do { \
+ if (!dcc_config.use_timer_poll) { \
+ if (test_and_clear_bit(0, &dcc_config.irq_disable)) \
+ enable_irq(dcc_config.tx_interrupt); \
+ } \
+ } while(0)
+
+#define RVIDCC_DISABLE_WRITE_INTERRUPT do { \
+ if (!dcc_config.use_timer_poll) { \
+ if (!test_and_set_bit(0, &dcc_config.irq_disable)) \
+ disable_irq(dcc_config.tx_interrupt); \
+ } \
+ } while(0)
+
+#define RVIDCC_ENABLE_READ_INTERRUPT do { \
+ if (!dcc_config.use_timer_poll) { \
+ if (test_and_clear_bit(1, &dcc_config.irq_disable)) \
+ enable_irq(dcc_config.rx_interrupt); \
+ } \
+ } while(0)
+
+#define RVIDCC_DISABLE_READ_INTERRUPT do { \
+ if (!dcc_config.use_timer_poll) { \
+ if (!test_and_set_bit(1, &dcc_config.irq_disable)) \
+ disable_irq(dcc_config.rx_interrupt); \
+ } \
+ } while(0)
+
+
+#endif /* RVIDCC_CONFIG_H */
diff --git a/arch/arm/common/rvidcc_interface.h b/arch/arm/common/rvidcc_interface.h
new file mode 100644
index 000000000000..9ccf439ce46e
--- /dev/null
+++ b/arch/arm/common/rvidcc_interface.h
@@ -0,0 +1,182 @@
+/*
+ Copyright (C) 2004-2007 ARM Limited.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ As a special exception, if other files instantiate templates or use macros
+ or inline functions from this file, or you compile this file and link it
+ with other works to produce a work based on this file, this file does not
+ by itself cause the resulting work to be covered by the GNU General Public
+ License. However the source code for this file must still be made available
+ in accordance with section (3) of the GNU General Public License.
+
+ This exception does not invalidate any other reasons why a work based on
+ this file might be covered by the GNU General Public License.
+*/
+
+#ifndef RVIDCC_INTERFACE_H
+#define RVIDCC_INTERFACE_H
+
+/*
+ * These are the functions provided by the generic section that the OS-dependent
+ * section is allowed to call
+ */
+
+#define RVIDCC_ERR_CORE_NOT_SUPPORTED 1
+
+/* Initialise DCC comms driver.
+ *
+ * Note that this triggers the request to the RVI for a MAC address when using
+ * virtual Ethernet. Returns 0 on success, RVIDCC_ERR_* on failure
+ */
+extern int rvidcc_init(void);
+
+/* Poll for DCC communications.
+ *
+ * If interrupts are not being used then this should be called periodically,
+ * otherwise this is the interrupt handler for DCC communications.
+ */
+extern void rvidcc_poll(void);
+
+#ifndef RVIDCC_RAW
+/* Process received DCC data.
+ *
+ * This function is only relevant if using virtual Ethernet. This is a
+ * 'bottom half' routine and processes the data captured in rvidcc_poll(). If
+ * using interrupts, this routine should be scheduled to be called at the next
+ * available opportunity once the interrupt handler has completed. If not
+ * using interrupts then this can be called immediately on notification of the
+ * arrival of incoming data.
+ */
+extern void rvidcc_process(void);
+#endif
+
+/* Handle low level reading of dcc information
+ *
+ * This should be called from the interrupt handler for the dcc read interrupt
+ * to read data from the DCC register. It is also called by rvidcc_poll.
+ */
+extern void rvidcc_read(void);
+
+/* Handle low level writing of dcc information
+ *
+ * This should be called from the interrupt handler for the dcc write interrupt
+ * to write data to the DCC register. It is also called by rvidcc_poll.
+ */
+extern void rvidcc_write(void);
+
+#ifdef RVIDCC_OUTBUF_DATA
+/* Get the number of bytes left to be sent out to the ICE */
+extern size_t rvidcc_outbuf_data(void);
+#endif
+
+/* Get the number of bytes available for reading from TTY.
+ *
+ * Zero is returned if no data is available.
+ */
+extern size_t rvidcc_serial_can_read(void);
+
+/* Get the number of bytes that can be written to the TTY.
+ *
+ * Zero is returned if no data can be written.
+ */
+extern size_t rvidcc_serial_can_write(void);
+
+/* Read and consume incoming TTY data.
+ *
+ * This is the primary TTY read routine.
+ */
+extern size_t rvidcc_read_serial(void *ptr, size_t len);
+
+/* Write and queue up outgoing TTY data.
+ *
+ * This is the primary TTY write routine.
+ */
+extern size_t rvidcc_write_serial(const void *ptr, size_t len);
+
+/* Send an IP packet out to the virtual network.
+ *
+ * The data provided must be a valid IP packet and must not contain an
+ * Ethernet header. The data provided is all copied, if there is space on the
+ * queue, othewise none of it is and the packet should be treated as still
+ * pending. The return value is the number of bytes copied.
+ */
+extern int rvidcc_transmit_ip_packet(void *packet, size_t length);
+
+
+#ifdef RVIDCC_SCAN_INPUT_FOR
+/* Scan pending incoming TTY data for a character.
+ *
+ * This is primarily provided for detecting ^C (interrupt) on pending incoming
+ * TTY data, e.g. to allow a gdb stub to be re-activated when a program is
+ * running. It should be called from 'bottom half' processing, or from a
+ * regular poll. The scan starts at the latest character received and works
+ * backwards for a maximum of 'span' characters.
+ */
+extern int rvidcc_scan_input_for(char c, size_t span);
+#endif
+
+
+/*
+ * Callbacks
+ * These are called from the generic section (in rvidcc.c) and should be provided
+ * by the OS-dependent section.
+ */
+
+
+/* DCC incoming data notification callback.
+ *
+ * This is called by rvidcc_poll() whenever new data is received on the DCC
+ * channel. It indicates rvidcc_process() should be called at the next
+ * available opportunity (and also rvidcc_scaninput_for() if required). If
+ * using interrupts, schedule a 'bottom half' or tasklet to run. If not using
+ * interrupts, rvidcc_process() can be called directly from here.
+ */
+extern void rvidcc_cb_notify(void);
+
+#ifndef RVIDCC_RAW
+
+/* Network IP packet received callback.
+ *
+ * This is called from rvidcc_process() whenever an IP packet is received. Note:
+ * this only contains IP data without any Ethernet headers. The data must be
+ * copied and passed on to the OS appropriately.
+ */
+extern void rvidcc_cb_ip_packet_received(void *packet, size_t length);
+
+/* Pseudo ARP callback.
+ *
+ * This is called from rvidcc_process() whenever the ICE needs to determine if
+ * it should direct IP packets for the given IP address to this processor.
+ * Return non-zero if this is the case, or zero otherwise. The parameter
+ * passed is a pointer to 4-byte array which is the binary IP address in
+ * network (big-endian) order.
+ */
+extern int rvidcc_cb_has_addr(unsigned char *ip_binary);
+
+/* Set Ethernet MAC address callback.
+ *
+ * This is called from rvidcc_process() whenever in response to _init_dcc()'s
+ * request to the ICE for a (globally unique) MAC address. This happens very
+ * soon after initialisation, and if a MAC is required, then the virtual
+ * Ethernet driver should not be considered fully up and running until this
+ * has been received. The parameter is a pointer to a 6-byte array which is
+ * the MAC address.
+ */
+extern void rvidcc_cb_set_mac_address(unsigned char *mac);
+
+#endif
+
+
+#endif // RVIDCC_INTERFACE_H
diff --git a/arch/arm/common/rvidcc_linux.c b/arch/arm/common/rvidcc_linux.c
new file mode 100644
index 000000000000..c07058314475
--- /dev/null
+++ b/arch/arm/common/rvidcc_linux.c
@@ -0,0 +1,1014 @@
+/*
+ Copyright (C) 2004-2007 ARM Limited.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ As a special exception, if other files instantiate templates or use macros
+ or inline functions from this file, or you compile this file and link it
+ with other works to produce a work based on this file, this file does not
+ by itself cause the resulting work to be covered by the GNU General Public
+ License. However the source code for this file must still be made available
+ in accordance with section (3) of the GNU General Public License.
+
+ This exception does not invalidate any other reasons why a work based on
+ this file might be covered by the GNU General Public License.
+*/
+
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/console.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <net/route.h>
+#include <asm/hardware/rvidcc.h>
+
+#ifdef CONFIG_DEBUG_DCC_KGDB
+#include <linux/kgdb.h>
+#endif
+
+#include "rvidcc_config.h"
+#include "rvidcc_interface.h"
+
+#define ARM_DCC_VER "1.0"
+#define ARM_TTY_NAME "ttyDCC"
+#define ARM_ETH_NAME "ethDCC"
+#define ARM_DCC_MAJOR 204
+#define ARM_DCC_MINOR 46
+#define ARM_DCC_POLL_PERIOD 1 /* jiffies per poll: used in timed polled (non-interrupt) mode */
+
+/* SMP not supported */
+#ifdef CONFIG_SMP
+#error DCC module does not support SMP
+#endif
+
+/* Interrupt defininitons for known achitectures that have them */
+#ifdef CONFIG_ARCH_INTEGRATOR
+
+#define DCC_RX_IRQ IRQ_CM_COMMRX
+#define DCC_TX_IRQ IRQ_CM_COMMTX
+
+#elif defined(CONFIG_ARCH_L7200)
+
+#define DCC_RX_IRQ IRQ_DEBUG_RX
+#define DCC_TX_IRQ IRQ_DEBUG_TX
+
+#elif defined(CONFIG_ARCH_LH7A404)
+
+#define DCC_RX_IRQ IRQ_COMMRX
+#define DCC_TX_IRQ IRQ_COMMTX
+
+#elif defined(CONFIG_ARCH_VERSATILE_PB)
+
+#define DCC_RX_IRQ IRQ_COMMRx
+#define DCC_TX_IRQ IRQ_COMMTx
+
+#else
+
+#define DCC_RX_IRQ 0
+#define DCC_TX_IRQ 0
+
+#endif
+
+/* This section contains the primary OS driver functions and definitions */
+
+/* Structure containing our internal state, etc. */
+
+struct dccconfig dcc_config =
+{
+ .rx_interrupt = DCC_RX_IRQ,
+ .tx_interrupt = DCC_TX_IRQ,
+ .use_timer_poll = 1, /* use timer poll until interrupts are enabled (if ever) */
+ .timer_poll_period = ARM_DCC_POLL_PERIOD
+};
+
+struct dccinfo
+{
+ struct tty_struct *tty;
+ int tty_opens;
+ int init_called;
+ int kernel_in_panic;
+ volatile unsigned long timerpoll_lock;
+ volatile unsigned long tx_locked;
+#ifndef CONFIG_DEBUG_DCC_RAW
+ struct net_device *netdev;
+ char mac_address[ETH_ALEN];
+ struct sk_buff *pending_skb;
+#endif
+};
+static struct dccinfo dcc_info;
+
+/* Module parameters - IRQ's etc. an be set on the boot line */
+module_param_named(rxirq, dcc_config.rx_interrupt, uint, 0444);
+module_param_named(txirq, dcc_config.tx_interrupt, uint, 0444);
+module_param_named(pollperiod, dcc_config.timer_poll_period, ulong, 0444);
+MODULE_PARM_DESC(rxirq, "DCC receive IRQ number");
+MODULE_PARM_DESC(txirq, "DCC transmit IRQ number");
+MODULE_PARM_DESC(pollperiod, "DCC timer polling period");
+
+/* Tasklet used as a "bottom half" - services data collected by interrupt or timed poll */
+static void dcc_tasklet_action(unsigned long data);
+static DECLARE_TASKLET(dcc_tasklet, dcc_tasklet_action, 0);
+static void dcc_poll(void);
+
+/*
+ * Write data on the serial channel if no other write is in progress
+ */
+static size_t dcc_write_serial(const void *ptr, size_t len)
+{
+ size_t res = 0;
+
+ if (!test_and_set_bit(0, &dcc_info.tx_locked))
+ {
+ res = rvidcc_write_serial(ptr, len);
+ clear_bit(0, &dcc_info.tx_locked);
+ }
+
+ return res;
+}
+
+#ifndef RVIDCC_RAW
+/*
+ * Send a network packet if no other write is in progress
+ */
+static int dcc_transmit_ip_packet(void *packet, size_t length)
+{
+ int res = 0;
+
+ if (!test_and_set_bit(0, &dcc_info.tx_locked))
+ {
+ res = rvidcc_transmit_ip_packet(packet, length);
+ clear_bit(0, &dcc_info.tx_locked);
+ }
+
+ return res;
+}
+#endif
+
+/* KGDB support
+ *
+ * KGDB and the console/tty channel are mutually exclusive,
+ * i.e. KGDB takes over the tty channel if it is enabled.
+ */
+#ifdef CONFIG_DEBUG_DCC_KGDB
+
+#define KGDB_BUF_SIZE 1024 /* needs to be big enough to hold one GDB packet */
+static int dcc_init_dcc(void);
+static void dcc_print_banner(void);
+static void dcc_tasklet_action(unsigned long data);
+
+static char kgdb_buf[KGDB_BUF_SIZE];
+static size_t kgdb_buf_chars = 0;
+
+/* KGDB getchar routine */
+static int kgdb_getDebugChar(void)
+{
+ char ch;
+
+ /* The rest of the system is frozen at this point, so this is all that is running */
+ while (!rvidcc_read_serial(&ch, 1))
+ {
+ rvidcc_poll();
+ dcc_tasklet_action(1);
+ }
+
+ return (int)ch;
+}
+
+/* KGDB flush routine - ensures each GDB packet is sent as one message (more efficient) */
+static void kgdb_flushDebugChar(void)
+{
+ size_t written = 0;
+
+ /* The rest of the system is frozen at this point, so this is all that is running */
+ while (written < kgdb_buf_chars)
+ {
+ written += dcc_write_serial(&kgdb_buf[written], kgdb_buf_chars - written);
+ rvidcc_poll();
+ dcc_tasklet_action(1);
+ }
+
+ kgdb_buf_chars = 0;
+}
+
+/* KGDB putchar routine */
+static void kgdb_putDebugChar(int chr)
+{
+ kgdb_buf[kgdb_buf_chars++] = (char)chr;
+ if (kgdb_buf_chars >= KGDB_BUF_SIZE)
+ kgdb_flushDebugChar();
+}
+
+/* KGDB initialisation routine - can be called very early in kernel startup */
+static int init_kgdbDcc(void)
+{
+ int err = 0;
+ if (!dcc_info.init_called)
+ {
+ dcc_print_banner();
+ err = dcc_init_dcc();
+ if (!err)
+ printk(KERN_INFO "kgdb: debugging over DCC enabled\n");
+ }
+
+ return err;
+}
+
+/* KGDB device driver structure - only one of these is allowed globally */
+struct kgdb_io kgdb_io_ops =
+{
+ .read_char = kgdb_getDebugChar,
+ .write_char = kgdb_putDebugChar,
+ .init = init_kgdbDcc,
+ .flush = kgdb_flushDebugChar
+};
+
+#else
+
+/* TTY device driver routines. Must peacefully co-exist with console support.
+ *
+ * Not present when KGDB is enabled. KGDB has its own driver format (above).
+ */
+/* TTY device open routine */
+static int dcc_chr_open(struct tty_struct *tty, struct file *filp)
+{
+ if (!dcc_info.tty_opens++)
+ dcc_info.tty = tty;
+
+ return 0;
+}
+
+/* TTY device close routine */
+static void dcc_chr_close(struct tty_struct *tty, struct file *filp)
+{
+ if (!--dcc_info.tty_opens)
+ dcc_info.tty = NULL;
+}
+
+/* TTY device write routine */
+static int dcc_chr_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ int bytes = 0;
+
+ if (count == 0)
+ return 0;
+
+ bytes = (int)dcc_write_serial(buf, count);
+
+ if (dcc_config.use_timer_poll)
+ dcc_poll();
+
+ return bytes;
+}
+
+/* TTY device routine to determine space left in output buffer */
+static int dcc_chr_write_room(struct tty_struct *tty)
+{
+ int space;
+
+ if (dcc_config.use_timer_poll)
+ dcc_poll();
+
+ space = rvidcc_serial_can_write() - 4 ; /* Leave 1 word spare for sentinel*/
+ return space > 0 ? space : 0;
+}
+
+/* TTY device routine to determine number of chars waiting in input buffer */
+static int dcc_chr_chars_in_buffer(struct tty_struct *tty)
+{
+ if (dcc_config.use_timer_poll)
+ dcc_poll();
+
+ return rvidcc_serial_can_read();
+}
+
+/* TTY device ioctl routine. Global and affects virtual Ethernet as well */
+static int dcc_chr_ioctl(struct tty_struct *tty, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret_val = 0;
+
+ switch (cmd) {
+ case DCC_SETPOLLPERIOD:
+ dcc_config.timer_poll_period = arg;
+ break;
+
+ case DCC_GETPOLLPERIOD:
+ ret_val = dcc_config.timer_poll_period;
+ break;
+
+ default:
+ ret_val = -ENOIOCTLCMD;
+ break;
+ }
+
+ return ret_val;
+}
+
+#endif /* CONFIG_DEBUG_DCC_KGDB */
+
+/* Network (virtual Ethernet over DCC) device driver routines
+ *
+ * Co-exists with KGDB, and will run when KGDB is at a breakpoint.
+ * Developer beware !
+ */
+#ifndef CONFIG_DEBUG_DCC_RAW
+
+/* Network device driver structure */
+static struct net_device* dcc_netdev;
+
+/* Callback from the universal DCC driver when a MAC address arrives from the RVI */
+void rvidcc_cb_set_mac_address(unsigned char *mac)
+{
+ memcpy(dcc_info.mac_address, mac, ETH_ALEN);
+
+ printk(KERN_INFO "DCC: MAC address %02x:%02x:%02x:%02x:%02x:%02x\n", dcc_info.mac_address[0],
+ dcc_info.mac_address[1], dcc_info.mac_address[2], dcc_info.mac_address[3],
+ dcc_info.mac_address[4], dcc_info.mac_address[5]);
+}
+
+
+/* Network device address validation */
+static int dcc_net_validate_addr(struct net_device *dev)
+{
+ /* Wait for the MAC address from the RVI */
+ unsigned long startjif = jiffies;
+ while (dcc_info.mac_address[0] == 0 &&
+ dcc_info.mac_address[1] == 0 &&
+ dcc_info.mac_address[2] == 0 &&
+ dcc_info.mac_address[3] == 0 &&
+ dcc_info.mac_address[4] == 0 &&
+ dcc_info.mac_address[5] == 0)
+ {
+ if (dcc_config.use_timer_poll)
+ dcc_poll();
+
+ /* Wait for a maximum of 1/2 a second */
+ if (startjif - jiffies > HZ/2)
+ {
+ printk(KERN_ERR "DCC: Virtual ethernet timed out waiting for MAC address\n");
+ break;
+ }
+ schedule();
+ }
+
+ memcpy(dev->dev_addr, dcc_info.mac_address, ETH_ALEN);
+
+ if (!is_valid_ether_addr(dev->dev_addr))
+ return -EINVAL;
+
+ return 0;
+}
+
+/* Network device open routine */
+static int dcc_net_open(struct net_device *dev)
+{
+ netif_start_queue(dev);
+ dcc_info.netdev = dev; /* mark as running */
+ return 0;
+}
+
+/* Network device close routine */
+static int dcc_net_close(struct net_device *dev)
+{
+ /* Discard any pending TX packet */
+ if (dcc_info.pending_skb)
+ {
+ dev->stats.tx_dropped++;
+ dev_kfree_skb(dcc_info.pending_skb);
+ dcc_info.pending_skb = NULL;
+ }
+
+ /* Mark as not running, to discard incoming packets */
+ dcc_info.netdev = NULL;
+
+ netif_stop_queue(dev);
+ return 0;
+}
+
+/* Called by universal driver when an IP packet is received */
+void rvidcc_cb_ip_packet_received(void *packet, size_t length)
+{
+ /* Drop the packet if the device isn't open */
+ if (dcc_info.netdev)
+ {
+ struct sk_buff *skb = dev_alloc_skb(length + 16);
+ struct ethhdr *buf;
+
+ if (!skb)
+ {
+ dcc_info.netdev->stats.rx_dropped++;
+ return;
+ }
+
+ /* Create Ethernet header */
+ skb_reserve(skb,2); /* Force 16 byte alignment */
+ buf = (struct ethhdr*)skb_put(skb, length + 14);
+ memcpy(buf->h_dest, dcc_info.mac_address, 6);
+ memcpy(&buf->h_source[3], &dcc_info.mac_address[3], 3);
+ buf->h_source[0] = 0x00;
+ buf->h_source[1] = 0x02;
+ buf->h_source[2] = 0xf7; /* RVI MAC address */
+ buf->h_proto = htons(ETH_P_IP);
+
+ /* Fill in the rest of the data */
+ memcpy((char*)buf + sizeof(struct ethhdr), packet, length);
+ skb->dev = dcc_info.netdev;
+ skb->protocol = eth_type_trans(skb, dcc_info.netdev);
+ dcc_info.netdev->stats.rx_packets++;
+ dcc_info.netdev->stats.rx_bytes += length+14;
+ netif_rx(skb);
+ }
+}
+
+/* Network device packet transmit routine */
+static int dcc_net_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ switch (ntohs(((struct ethhdr*)skb->data)->h_proto))
+ {
+ case ETH_P_ARP: /* ARP protocol */
+ {
+ struct sk_buff *skbreply = dev_alloc_skb(skb->len);
+ struct ethhdr *buf;
+ struct arphdr *arp;
+ unsigned char *arpdata, *origarpdata;
+
+ if (!skbreply)
+ {
+ dcc_info.netdev->stats.tx_dropped++;
+ dev_kfree_skb(skb);
+ return 0;
+ }
+
+ /* Set up manufactured ARP reply data */
+ buf = (struct ethhdr*)skb_put(skbreply, skb->len);
+ arp = (struct arphdr*)((char*)buf + sizeof(struct ethhdr));
+ arpdata = (unsigned char*)((char*)arp + sizeof(struct arphdr));
+ origarpdata = skb->data + sizeof(struct ethhdr) + sizeof(struct arphdr);
+ memcpy(buf, skb->data, skb->len);
+ memcpy(buf->h_dest, dcc_info.mac_address, 6);
+ memcpy(&buf->h_source[3], &dcc_info.mac_address[3], 3);
+ buf->h_source[0] = 0x00;
+ buf->h_source[1] = 0x02;
+ buf->h_source[2] = 0xf7; /* RVI MAC address */
+ arp->ar_op = htons(ARPOP_REPLY);
+ memcpy(&arpdata[0], buf->h_source, ETH_ALEN);
+ memcpy(&arpdata[10], buf->h_dest, ETH_ALEN);
+ memcpy(&arpdata[6], &origarpdata[16], 4);
+ memcpy(&arpdata[16], &origarpdata[6], 4);
+
+ skbreply->dev = dcc_info.netdev;
+ skbreply->protocol = eth_type_trans(skbreply, dcc_info.netdev);
+ dcc_info.netdev->stats.rx_packets++;
+ dcc_info.netdev->stats.rx_bytes += skb->len;
+ dcc_info.netdev->stats.tx_packets++;
+ dcc_info.netdev->stats.tx_bytes += skb->len;
+ dev_kfree_skb(skb);
+ netif_rx(skbreply);
+ break;
+ }
+ case ETH_P_IP: /* IP protocol */
+ {
+ if (dcc_transmit_ip_packet(&skb->data[14], skb->len-14)) /* discard eth header */
+ {
+ dcc_info.netdev->stats.tx_packets++;
+ dcc_info.netdev->stats.tx_bytes += skb->len;
+ dev_kfree_skb(skb);
+ }
+ else
+ {
+ dcc_info.pending_skb = skb;
+ netif_stop_queue(dev);
+ }
+
+ if (dcc_config.use_timer_poll)
+ dcc_poll();
+
+ break;
+ }
+ default:
+ printk(KERN_WARNING "DCC: dropping packet ethertype 0x%04x\n",
+ (int)ntohs(((struct ethhdr*)skb->data)->h_proto));
+ dcc_info.netdev->stats.tx_dropped++;
+ dev_kfree_skb(skb);
+ }
+
+ return 0;
+}
+
+/* Universal driver callback to handle pseudo-ARP */
+int rvidcc_cb_has_addr(unsigned char *ip_binary)
+{
+ u32 ip;
+ memcpy(&ip, ip_binary, 4);
+
+ if (!dcc_info.netdev || IN_LOOPBACK(ip) || IN_MULTICAST(ip))
+ return 0;
+
+ /* Return 1 if address is local to the target */
+ return inet_addr_type(dev_net(dcc_info.netdev), ip) == RTN_LOCAL ? 1 : 0;
+}
+
+/* Network device initialisation routine */
+static void dcc_net_init(struct net_device *dev)
+{
+ /* Like normal Ethernet, but with specific differences */
+ ether_setup(dev);
+ dev->tx_queue_len = 3;
+
+ /* Driver routines */
+ dev->validate_addr = dcc_net_validate_addr;
+ dev->open = dcc_net_open;
+ dev->stop = dcc_net_close;
+ dev->hard_start_xmit = dcc_net_xmit;
+}
+
+#endif /* !CONFIG_DEBUG_DCC_RAW */
+
+/* Data transfer handling functionality
+ *
+ * We support interrupt and timer polled functionality
+ * if interrupts are not available.
+ */
+static void dcc_timer_poll(unsigned long arg);
+
+static struct timer_list dcc_timer = {
+ function: dcc_timer_poll
+};
+
+/* Poll routine for when interrupts are not used */
+static void dcc_poll(void)
+{
+ if (!test_and_set_bit(0, &dcc_info.timerpoll_lock) &&
+ !dcc_info.kernel_in_panic)
+ {
+ rvidcc_poll();
+ clear_bit(0, &dcc_info.timerpoll_lock);
+ }
+}
+
+/* Self-recheduling timer poll routine */
+static void dcc_timer_poll(unsigned long arg)
+{
+ dcc_poll();
+ dcc_timer.expires = jiffies + dcc_config.timer_poll_period;
+ add_timer(&dcc_timer);
+}
+
+/* DCC interrupt handler */
+static irqreturn_t dcc_read_interrupt(int irq, void *devid)
+{
+ if (!dcc_info.kernel_in_panic)
+ rvidcc_read();
+ return IRQ_HANDLED;
+}
+
+/* DCC interrupt handler */
+static irqreturn_t dcc_write_interrupt(int irq, void *devid)
+{
+ if (!dcc_info.kernel_in_panic)
+ rvidcc_write();
+ return IRQ_HANDLED;
+}
+
+/* Routine to process incoming DCC data from interrupt or timer poll */
+static void dcc_tasklet_action(unsigned long data)
+{
+#ifndef CONFIG_DEBUG_DCC_RAW
+ rvidcc_process();
+
+ /* Try to transmit any pending packet */
+ if (dcc_info.pending_skb && dcc_info.netdev)
+ {
+ if (dcc_transmit_ip_packet(&dcc_info.pending_skb->data[14], dcc_info.pending_skb->len-14))
+ {
+ dcc_info.netdev->stats.tx_packets++;
+ dcc_info.netdev->stats.tx_bytes += dcc_info.pending_skb->len;
+ dev_kfree_skb(dcc_info.pending_skb);
+ dcc_info.pending_skb = NULL;
+ netif_wake_queue(dcc_info.netdev);
+ }
+ }
+#endif
+
+ /* tasklet can only run on one CPU at a time and this is the only place
+ * we do a serial read, so no locking needed here */
+#ifdef CONFIG_DEBUG_DCC_KGDB
+ if (data != 1 && rvidcc_serial_can_read())
+ {
+ /* Absorb ^C before triggering a hardcoded breakpoint */
+ if (rvidcc_scan_input_for(0x03, 1))
+ {
+ char buf;
+ rvidcc_read_serial(&buf, 1);
+ }
+ breakpoint();
+ }
+#else
+ /* Receive any bytes that may be pending if the device has been opened */
+ if (dcc_info.tty)
+ {
+ size_t data_available = rvidcc_serial_can_read();
+ if (data_available)
+ {
+ unsigned char* buf;
+ int len = tty_prepare_flip_string(dcc_info.tty, &buf, data_available);
+ if (len)
+ {
+ rvidcc_read_serial(buf, len);
+ tty_flip_buffer_push(dcc_info.tty);
+ }
+ }
+ }
+#endif
+}
+
+/* Universal driver callback called when data has arrived on DCC */
+void rvidcc_cb_notify()
+{
+ tasklet_schedule(&dcc_tasklet);
+}
+
+/* Universal initialisation - ensures init only happens once */
+static int dcc_init_dcc(void)
+{
+ int err = 0;
+ if (!dcc_info.init_called)
+ {
+ err = rvidcc_init();
+ if (err == 0)
+ dcc_info.init_called = 1;
+ else
+ return -ENODEV;
+ }
+
+ return err;
+}
+
+/* Banner to be printed on initialisation */
+static void dcc_print_banner(void)
+{
+ printk(KERN_INFO "RealView ICE DCC device driver %s "
+#ifdef CONFIG_DEBUG_DCC_RAW
+ "[raw mode] "
+#else
+ "[tty_eth mode] "
+#endif
+ "(C)2004-2007 ARM Limited\n", ARM_DCC_VER);
+}
+
+static void dcc_drain_outbuf(void)
+{
+ size_t bytesPending;
+ size_t lastBytesPending = 0;
+ int failcount = 0;
+
+ while ((bytesPending = rvidcc_outbuf_data()) > 0)
+ {
+ if (bytesPending >= lastBytesPending)
+ ++failcount;
+
+ if (failcount > 10)
+ {
+ /* The queue isn't draining: either the connection to the RVI is
+ * lost or we can't process messages fast enough. Either way, it's
+ * bad to block forever, so bail out */
+ break;
+ }
+
+ lastBytesPending = bytesPending;
+
+ rvidcc_poll();
+ }
+}
+
+#ifndef CONFIG_DEBUG_DCC_KGDB
+
+/* TTY device driver structure */
+static const struct tty_operations dcc_tty_ops =
+{
+ .open = dcc_chr_open,
+ .close = dcc_chr_close,
+ .write = dcc_chr_write,
+ .write_room = dcc_chr_write_room,
+ .chars_in_buffer = dcc_chr_chars_in_buffer,
+ .ioctl = dcc_chr_ioctl
+};
+
+static struct tty_driver *dcc_tty_driver;
+
+/* Create and install TTY driver */
+static int dcc_init_tty_driver(void)
+{
+ int error = 0;
+
+ dcc_tty_driver = alloc_tty_driver(1);
+ if (!dcc_tty_driver)
+ return -ENOMEM;
+ dcc_tty_driver->owner = THIS_MODULE;
+ dcc_tty_driver->driver_name = ARM_TTY_NAME;
+ dcc_tty_driver->name = ARM_TTY_NAME;
+ dcc_tty_driver->major = ARM_DCC_MAJOR;
+ dcc_tty_driver->minor_start = ARM_DCC_MINOR;
+ dcc_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ dcc_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ dcc_tty_driver->flags = TTY_DRIVER_REAL_RAW;
+ dcc_tty_driver->init_termios = tty_std_termios;
+ tty_set_operations(dcc_tty_driver, &dcc_tty_ops);
+ if ((error = tty_register_driver(dcc_tty_driver)))
+ {
+ printk(KERN_ERR "DCC: Can't register tty device %d, error = %d\n",
+ ARM_DCC_MINOR, error);
+ put_tty_driver(dcc_tty_driver);
+ dcc_tty_driver = NULL;
+ return error;
+ }
+ return 0;
+}
+
+/* Console device print routine */
+static void dcc_console_print(struct console *co, const char *buf, unsigned count)
+{
+ unsigned int bytes = 0;
+
+ /* Send message out */
+ bytes = (unsigned int)dcc_write_serial(buf, count);
+
+ /* If buffer is full stall the system until there is space */
+ /* A flood of printk's usually means the system is dying anyway */
+ if (bytes < count)
+ {
+ unsigned long flags;
+ local_irq_save(flags);
+
+ while (bytes < count)
+ {
+ rvidcc_poll();
+ bytes += (unsigned int)dcc_write_serial(buf + bytes, count - bytes);
+ }
+
+ local_irq_restore(flags);
+ }
+
+ /* Do an immediate flush if kernel has panicked */
+ if (dcc_info.kernel_in_panic)
+ {
+ unsigned long flags;
+ local_irq_save(flags);
+
+ dcc_drain_outbuf();
+
+ local_irq_restore(flags);
+ }
+}
+
+/* Console device initialisation routine */
+static struct tty_driver* dcc_console_device(struct console *co, int *index)
+{
+ *index = 0;
+ return dcc_tty_driver;
+}
+
+/* Console device driver */
+static struct console dcc_con_driver =
+{
+ .name = ARM_TTY_NAME,
+ .write = dcc_console_print,
+ .device = dcc_console_device,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+
+/* Routine to ensure DCC is flushed on reboot or kernel panic */
+static int dcc_reboot_handler(struct notifier_block *this,
+ unsigned long event, void *unused)
+{
+ unsigned long flags;
+ local_irq_save(flags);
+
+ dcc_drain_outbuf();
+
+ local_irq_restore(flags);
+
+ return NOTIFY_OK;
+}
+
+/* Reboot notifier structure */
+static struct notifier_block dcc_reboot_notifier =
+{
+ dcc_reboot_handler,
+ NULL,
+ 0
+};
+
+/* Called when kernel enters the panic state */
+static int dcc_panic_handler(struct notifier_block *this,
+ unsigned long event, void *unused)
+{
+ dcc_info.kernel_in_panic = 1;
+ return dcc_reboot_handler(this, event, unused);
+}
+
+/* Panic notifier structure */
+static struct notifier_block dcc_panic_notifier =
+{
+ .notifier_call = dcc_panic_handler,
+ .next = NULL,
+ .priority = 0
+};
+
+/* Early console initialization. Preceeds driver initialization. */
+static int __init dcc_console_init(void)
+{
+ int err = 0;
+
+ if (!dcc_info.init_called)
+ {
+ dcc_print_banner();
+ err = dcc_init_dcc();
+ }
+
+ if (!err)
+ {
+ register_reboot_notifier(&dcc_reboot_notifier);
+ atomic_notifier_chain_register(&panic_notifier_list, &dcc_panic_notifier);
+ register_console(&dcc_con_driver);
+ }
+
+ return err;
+}
+#endif /* !CONFIG_DEBUG_DCC_KGDB */
+
+/* Primary module (and devices) initialisation routine */
+int __init dcc_init(void)
+{
+ int ret = 0;
+
+ if (!dcc_info.init_called)
+ dcc_print_banner();
+
+ /* Detect whether timer polling when required */
+ if (dcc_config.rx_interrupt == dcc_config.tx_interrupt)
+ {
+ dcc_config.use_timer_poll = 1;
+
+ if (dcc_config.rx_interrupt != 0)
+ printk(KERN_ERR "DCC: IRQ numbers must be different for Rx and Tx !!!!!\n");
+ }
+ else
+ dcc_config.use_timer_poll = 0;
+
+#ifndef CONFIG_DEBUG_DCC_KGDB
+ ret = dcc_init_tty_driver();
+#endif
+
+#ifndef CONFIG_DEBUG_DCC_RAW
+ if (!ret)
+ {
+ /* Install network device driver */
+ dcc_netdev = alloc_netdev(0, ARM_ETH_NAME, dcc_net_init);
+ if (dcc_netdev == NULL)
+ {
+ printk(KERN_ERR "DCC: Can't allocate net device\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+ else
+ {
+ ret = register_netdev(dcc_netdev);
+
+ if (ret)
+ {
+ printk(KERN_ERR "DCC: Can't register net device\n");
+ free_netdev(dcc_netdev);
+ goto error;
+ }
+ }
+ }
+#endif
+
+ /* Initiate interrupts or timer polling as necessary */
+ if (!ret)
+ {
+ if (!dcc_config.use_timer_poll)
+ {
+ ret = dcc_init_dcc();
+
+ if (ret)
+ {
+ goto error;
+ }
+ else
+ {
+ if (request_irq(dcc_config.rx_interrupt, dcc_read_interrupt, 0, "dcc:rx", NULL) ||
+ request_irq(dcc_config.tx_interrupt, dcc_write_interrupt, 0, "dcc:tx", NULL))
+ {
+ printk(KERN_ERR "DCC: Can't install interrupt handlers for IRQ%u and/or IRQ%u\n",
+ dcc_config.rx_interrupt, dcc_config.tx_interrupt);
+ /* unregister tty & netdev drivers on error */
+ ret = -EBUSY;
+ goto error;
+ }
+ else
+ printk(KERN_INFO "DCC: Using IRQ%u (Rx) and IRQ%u (Tx)\n",
+ dcc_config.rx_interrupt, dcc_config.tx_interrupt);
+ }
+ }
+ else
+ {
+ init_timer(&dcc_timer);
+ printk(KERN_INFO "DCC: Using timer polled mode\n");
+
+ ret = dcc_init_dcc();
+ if (ret)
+ {
+ del_timer_sync(&dcc_timer);
+ goto error;
+ }
+ else
+ {
+ dcc_timer.expires = jiffies + dcc_config.timer_poll_period;
+ add_timer(&dcc_timer);
+ }
+ }
+ }
+
+ return ret;
+
+error:
+
+#ifndef CONFIG_DEBUG_DCC_KGDB
+ if (dcc_tty_driver)
+ {
+ tty_unregister_driver(dcc_tty_driver);
+ put_tty_driver(dcc_tty_driver);
+ dcc_tty_driver = NULL;
+ }
+#endif
+
+#ifndef CONFIG_DEBUG_DCC_RAW
+ if (dcc_netdev)
+ {
+ unregister_netdev(dcc_netdev);
+ free_netdev(dcc_netdev);
+ dcc_netdev = NULL;
+ }
+#endif
+
+ return ret;
+}
+
+/* Module cleanup (exit) routine - only called if compiled as a module */
+void __exit dcc_cleanup(void)
+{
+ if (!dcc_config.use_timer_poll)
+ {
+ free_irq(dcc_config.rx_interrupt, NULL);
+ free_irq(dcc_config.tx_interrupt, NULL);
+ }
+ else
+ del_timer_sync(&dcc_timer);
+
+#ifndef CONFIG_DEBUG_DCC_KGDB
+ tty_unregister_driver(dcc_tty_driver);
+ put_tty_driver(dcc_tty_driver);
+#endif
+
+#ifndef CONFIG_DEBUG_DCC_RAW
+ unregister_netdev(dcc_netdev);
+ free_netdev(dcc_netdev);
+#endif
+
+#ifndef CONFIG_DEBUG_DCC_KGDB
+ unregister_console(&dcc_con_driver);
+ unregister_reboot_notifier(&dcc_reboot_notifier);
+ atomic_notifier_chain_unregister(&panic_notifier_list, &dcc_panic_notifier);
+#endif
+
+ dcc_drain_outbuf();
+}
+
+#ifndef CONFIG_DEBUG_DCC_KGDB
+console_initcall(dcc_console_init);
+#endif
+module_init(dcc_init);
+module_exit(dcc_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("ARM Ltd");
+MODULE_DESCRIPTION("Driver for the ARM Debug Communications Channel");
+MODULE_SUPPORTED_DEVICE(ARM_TTY_NAME);