diff options
author | Gary King <gking@nvidia.com> | 2009-12-07 16:18:05 -0800 |
---|---|---|
committer | Gary King <gking@nvidia.com> | 2009-12-07 16:18:05 -0800 |
commit | bb11d46b72a4a2b53f890faa970a380cf0084bf2 (patch) | |
tree | 2d9d2955e069fed69febbe567a835fcc48ec6ec6 /arch/arm/common | |
parent | e1b5e49debba7174e7b9c48195de8abfd54911dd (diff) | |
parent | 74ece4056571443eef30d4dff62180944b5a39d8 (diff) |
Merge commit 'arm/2.6.28-arm' into android-tegra-2.6.29
Conflicts:
MAINTAINERS
arch/arm/Kconfig
arch/arm/Makefile
arch/arm/boot/compressed/head.S
arch/arm/common/Makefile
arch/arm/configs/realview-smp_defconfig
arch/arm/configs/realview_defconfig
arch/arm/configs/versatile_defconfig
arch/arm/include/asm/elf.h
arch/arm/include/asm/uaccess.h
arch/arm/kernel/module.c
arch/arm/kernel/signal.c
arch/arm/mach-realview/Kconfig
arch/arm/mach-realview/Makefile
arch/arm/mach-realview/core.c
arch/arm/mach-realview/core.h
arch/arm/mach-realview/include/mach/board-pba8.h
arch/arm/mach-realview/include/mach/debug-macro.S
arch/arm/mach-realview/include/mach/hardware.h
arch/arm/mach-realview/include/mach/irqs.h
arch/arm/mach-realview/include/mach/memory.h
arch/arm/mach-realview/include/mach/uncompress.h
arch/arm/mach-realview/localtimer.c
arch/arm/mach-realview/platsmp.c
arch/arm/mach-realview/realview_eb.c
arch/arm/mach-realview/realview_pb1176.c
arch/arm/mach-realview/realview_pb11mp.c
arch/arm/mach-realview/realview_pba8.c
arch/arm/mm/Kconfig
arch/arm/mm/copypage-v6.c
arch/arm/mm/dma-mapping.c
arch/arm/mm/proc-v7.S
arch/arm/oprofile/op_model_mpcore.c
arch/arm/tools/mach-types
arch/arm/vfp/vfpmodule.c
drivers/mtd/maps/integrator-flash.c
drivers/net/smsc911x.c
drivers/net/smsc911x.h
Diffstat (limited to 'arch/arm/common')
-rw-r--r-- | arch/arm/common/Kconfig | 3 | ||||
-rw-r--r-- | arch/arm/common/Makefile | 3 | ||||
-rw-r--r-- | arch/arm/common/nvic.c | 99 | ||||
-rw-r--r-- | arch/arm/common/rvidcc_common.c | 794 | ||||
-rw-r--r-- | arch/arm/common/rvidcc_config.h | 166 | ||||
-rw-r--r-- | arch/arm/common/rvidcc_interface.h | 182 | ||||
-rw-r--r-- | arch/arm/common/rvidcc_linux.c | 1014 |
7 files changed, 2261 insertions, 0 deletions
diff --git a/arch/arm/common/Kconfig b/arch/arm/common/Kconfig index a2cd9beaf37d..1ecf3f9fb375 100644 --- a/arch/arm/common/Kconfig +++ b/arch/arm/common/Kconfig @@ -4,6 +4,9 @@ config ARM_GIC config ARM_VIC bool +config ARM_NVIC + bool + config ICST525 bool diff --git a/arch/arm/common/Makefile b/arch/arm/common/Makefile index 7cb7961d81cb..0adef7fca2f9 100644 --- a/arch/arm/common/Makefile +++ b/arch/arm/common/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_ARM_GIC) += gic.o obj-$(CONFIG_ARM_VIC) += vic.o +obj-$(CONFIG_ARM_NVIC) += nvic.o obj-$(CONFIG_ICST525) += icst525.o obj-$(CONFIG_ICST307) += icst307.o obj-$(CONFIG_SA1111) += sa1111.o @@ -18,3 +19,5 @@ obj-$(CONFIG_ARCH_IXP2000) += uengine.o obj-$(CONFIG_ARCH_IXP23XX) += uengine.o obj-$(CONFIG_PCI_HOST_ITE8152) += it8152.o obj-$(CONFIG_COMMON_CLKDEV) += clkdev.o +obj-$(CONFIG_DEBUG_RVIDCC) += rvidcc.o +rvidcc-objs := rvidcc_common.o rvidcc_linux.o diff --git a/arch/arm/common/nvic.c b/arch/arm/common/nvic.c new file mode 100644 index 000000000000..ea40cc8c7927 --- /dev/null +++ b/arch/arm/common/nvic.c @@ -0,0 +1,99 @@ +/* + * linux/arch/arm/common/nvic.c + * + * Copyright (C) 2008 ARM Limited, All Rights Reserved. + * + * 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. + * + * Support for the Nested Vectored Interrupt Controller found on the + * ARMv7-M CPUs (Cortex-M3) + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/smp.h> + +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/mach/irq.h> +#include <asm/hardware/nvic.h> + +static DEFINE_SPINLOCK(irq_controller_lock); + +/* + * Routines to acknowledge, disable and enable interrupts + * + * Linux assumes that when we're done with an interrupt we need to + * unmask it, in the same way we need to unmask an interrupt when + * we first enable it. + * + * The NVIC has a separate notion of "end of interrupt" to re-enable + * an interrupt after handling, in order to support hardware + * prioritisation. + * + * We can make the NVIC behave in the way that Linux expects by making + * our "acknowledge" routine disable the interrupt, then mark it as + * complete. + */ +static void nvic_ack_irq(unsigned int irq) +{ + u32 mask = 1 << (irq % 32); + + spin_lock(&irq_controller_lock); + writel(mask, NVIC_CLEAR_ENABLE + irq / 32 * 4); + spin_unlock(&irq_controller_lock); +} + +static void nvic_mask_irq(unsigned int irq) +{ + u32 mask = 1 << (irq % 32); + + spin_lock(&irq_controller_lock); + writel(mask, NVIC_CLEAR_ENABLE + irq / 32 * 4); + spin_unlock(&irq_controller_lock); +} + +static void nvic_unmask_irq(unsigned int irq) +{ + u32 mask = 1 << (irq % 32); + + spin_lock(&irq_controller_lock); + writel(mask, NVIC_SET_ENABLE + irq / 32 * 4); + spin_unlock(&irq_controller_lock); +} + +static struct irq_chip nvic_chip = { + .name = "NVIC", + .ack = nvic_ack_irq, + .mask = nvic_mask_irq, + .unmask = nvic_unmask_irq, +}; + +void __init nvic_init(void) +{ + unsigned int max_irq, i; + + max_irq = ((readl(NVIC_INTR_CTRL) & 0x1f) + 1) * 32; + + /* + * Disable all interrupts + */ + for (i = 0; i < max_irq / 32; i++) + writel(~0, NVIC_CLEAR_ENABLE + i * 4); + + /* + * Set priority on all interrupts. + */ + for (i = 0; i < max_irq; i += 4) + writel(0, NVIC_PRIORITY + i); + + /* + * Setup the Linux IRQ subsystem. + */ + for (i = 0; i < NR_IRQS; i++) { + set_irq_chip(i, &nvic_chip); + set_irq_handler(i, handle_level_irq); + set_irq_flags(i, IRQF_VALID | IRQF_PROBE); + } +} 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); |