diff options
Diffstat (limited to 'arch/c6x/kernel')
-rw-r--r-- | arch/c6x/kernel/Makefile | 12 | ||||
-rw-r--r-- | arch/c6x/kernel/asm-offsets.c | 123 | ||||
-rw-r--r-- | arch/c6x/kernel/c6x_ksyms.c | 66 | ||||
-rw-r--r-- | arch/c6x/kernel/devicetree.c | 53 | ||||
-rw-r--r-- | arch/c6x/kernel/dma.c | 153 | ||||
-rw-r--r-- | arch/c6x/kernel/entry.S | 803 | ||||
-rw-r--r-- | arch/c6x/kernel/head.S | 84 | ||||
-rw-r--r-- | arch/c6x/kernel/irq.c | 728 | ||||
-rw-r--r-- | arch/c6x/kernel/module.c | 123 | ||||
-rw-r--r-- | arch/c6x/kernel/process.c | 265 | ||||
-rw-r--r-- | arch/c6x/kernel/ptrace.c | 187 | ||||
-rw-r--r-- | arch/c6x/kernel/setup.c | 510 | ||||
-rw-r--r-- | arch/c6x/kernel/signal.c | 377 | ||||
-rw-r--r-- | arch/c6x/kernel/soc.c | 91 | ||||
-rw-r--r-- | arch/c6x/kernel/switch_to.S | 74 | ||||
-rw-r--r-- | arch/c6x/kernel/sys_c6x.c | 74 | ||||
-rw-r--r-- | arch/c6x/kernel/time.c | 65 | ||||
-rw-r--r-- | arch/c6x/kernel/traps.c | 423 | ||||
-rw-r--r-- | arch/c6x/kernel/vectors.S | 81 | ||||
-rw-r--r-- | arch/c6x/kernel/vmlinux.lds.S | 162 |
20 files changed, 4454 insertions, 0 deletions
diff --git a/arch/c6x/kernel/Makefile b/arch/c6x/kernel/Makefile new file mode 100644 index 000000000000..580a515a9443 --- /dev/null +++ b/arch/c6x/kernel/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for arch/c6x/kernel/ +# + +extra-y := head.o vmlinux.lds + +obj-y := process.o traps.o irq.o signal.o ptrace.o +obj-y += setup.o sys_c6x.o time.o devicetree.o +obj-y += switch_to.o entry.o vectors.o c6x_ksyms.o +obj-y += soc.o dma.o + +obj-$(CONFIG_MODULES) += module.o diff --git a/arch/c6x/kernel/asm-offsets.c b/arch/c6x/kernel/asm-offsets.c new file mode 100644 index 000000000000..759ad6d207b6 --- /dev/null +++ b/arch/c6x/kernel/asm-offsets.c @@ -0,0 +1,123 @@ +/* + * Generate definitions needed by assembly language modules. + * This code generates raw asm output which is post-processed + * to extract and format the required data. + */ + +#include <linux/sched.h> +#include <linux/thread_info.h> +#include <asm/procinfo.h> +#include <linux/kbuild.h> +#include <linux/unistd.h> + +void foo(void) +{ + OFFSET(REGS_A16, pt_regs, a16); + OFFSET(REGS_A17, pt_regs, a17); + OFFSET(REGS_A18, pt_regs, a18); + OFFSET(REGS_A19, pt_regs, a19); + OFFSET(REGS_A20, pt_regs, a20); + OFFSET(REGS_A21, pt_regs, a21); + OFFSET(REGS_A22, pt_regs, a22); + OFFSET(REGS_A23, pt_regs, a23); + OFFSET(REGS_A24, pt_regs, a24); + OFFSET(REGS_A25, pt_regs, a25); + OFFSET(REGS_A26, pt_regs, a26); + OFFSET(REGS_A27, pt_regs, a27); + OFFSET(REGS_A28, pt_regs, a28); + OFFSET(REGS_A29, pt_regs, a29); + OFFSET(REGS_A30, pt_regs, a30); + OFFSET(REGS_A31, pt_regs, a31); + + OFFSET(REGS_B16, pt_regs, b16); + OFFSET(REGS_B17, pt_regs, b17); + OFFSET(REGS_B18, pt_regs, b18); + OFFSET(REGS_B19, pt_regs, b19); + OFFSET(REGS_B20, pt_regs, b20); + OFFSET(REGS_B21, pt_regs, b21); + OFFSET(REGS_B22, pt_regs, b22); + OFFSET(REGS_B23, pt_regs, b23); + OFFSET(REGS_B24, pt_regs, b24); + OFFSET(REGS_B25, pt_regs, b25); + OFFSET(REGS_B26, pt_regs, b26); + OFFSET(REGS_B27, pt_regs, b27); + OFFSET(REGS_B28, pt_regs, b28); + OFFSET(REGS_B29, pt_regs, b29); + OFFSET(REGS_B30, pt_regs, b30); + OFFSET(REGS_B31, pt_regs, b31); + + OFFSET(REGS_A0, pt_regs, a0); + OFFSET(REGS_A1, pt_regs, a1); + OFFSET(REGS_A2, pt_regs, a2); + OFFSET(REGS_A3, pt_regs, a3); + OFFSET(REGS_A4, pt_regs, a4); + OFFSET(REGS_A5, pt_regs, a5); + OFFSET(REGS_A6, pt_regs, a6); + OFFSET(REGS_A7, pt_regs, a7); + OFFSET(REGS_A8, pt_regs, a8); + OFFSET(REGS_A9, pt_regs, a9); + OFFSET(REGS_A10, pt_regs, a10); + OFFSET(REGS_A11, pt_regs, a11); + OFFSET(REGS_A12, pt_regs, a12); + OFFSET(REGS_A13, pt_regs, a13); + OFFSET(REGS_A14, pt_regs, a14); + OFFSET(REGS_A15, pt_regs, a15); + + OFFSET(REGS_B0, pt_regs, b0); + OFFSET(REGS_B1, pt_regs, b1); + OFFSET(REGS_B2, pt_regs, b2); + OFFSET(REGS_B3, pt_regs, b3); + OFFSET(REGS_B4, pt_regs, b4); + OFFSET(REGS_B5, pt_regs, b5); + OFFSET(REGS_B6, pt_regs, b6); + OFFSET(REGS_B7, pt_regs, b7); + OFFSET(REGS_B8, pt_regs, b8); + OFFSET(REGS_B9, pt_regs, b9); + OFFSET(REGS_B10, pt_regs, b10); + OFFSET(REGS_B11, pt_regs, b11); + OFFSET(REGS_B12, pt_regs, b12); + OFFSET(REGS_B13, pt_regs, b13); + OFFSET(REGS_DP, pt_regs, dp); + OFFSET(REGS_SP, pt_regs, sp); + + OFFSET(REGS_TSR, pt_regs, tsr); + OFFSET(REGS_ORIG_A4, pt_regs, orig_a4); + + DEFINE(REGS__END, sizeof(struct pt_regs)); + BLANK(); + + OFFSET(THREAD_PC, thread_struct, pc); + OFFSET(THREAD_B15_14, thread_struct, b15_14); + OFFSET(THREAD_A15_14, thread_struct, a15_14); + OFFSET(THREAD_B13_12, thread_struct, b13_12); + OFFSET(THREAD_A13_12, thread_struct, a13_12); + OFFSET(THREAD_B11_10, thread_struct, b11_10); + OFFSET(THREAD_A11_10, thread_struct, a11_10); + OFFSET(THREAD_RICL_ICL, thread_struct, ricl_icl); + BLANK(); + + OFFSET(TASK_STATE, task_struct, state); + BLANK(); + + OFFSET(THREAD_INFO_FLAGS, thread_info, flags); + OFFSET(THREAD_INFO_PREEMPT_COUNT, thread_info, preempt_count); + BLANK(); + + /* These would be unneccessary if we ran asm files + * through the preprocessor. + */ + DEFINE(KTHREAD_SIZE, THREAD_SIZE); + DEFINE(KTHREAD_SHIFT, THREAD_SHIFT); + DEFINE(KTHREAD_START_SP, THREAD_START_SP); + DEFINE(ENOSYS_, ENOSYS); + DEFINE(NR_SYSCALLS_, __NR_syscalls); + + DEFINE(_TIF_SYSCALL_TRACE, (1<<TIF_SYSCALL_TRACE)); + DEFINE(_TIF_NOTIFY_RESUME, (1<<TIF_NOTIFY_RESUME)); + DEFINE(_TIF_SIGPENDING, (1<<TIF_SIGPENDING)); + DEFINE(_TIF_NEED_RESCHED, (1<<TIF_NEED_RESCHED)); + DEFINE(_TIF_POLLING_NRFLAG, (1<<TIF_POLLING_NRFLAG)); + + DEFINE(_TIF_ALLWORK_MASK, TIF_ALLWORK_MASK); + DEFINE(_TIF_WORK_MASK, TIF_WORK_MASK); +} diff --git a/arch/c6x/kernel/c6x_ksyms.c b/arch/c6x/kernel/c6x_ksyms.c new file mode 100644 index 000000000000..0ba3e0bba3e1 --- /dev/null +++ b/arch/c6x/kernel/c6x_ksyms.c @@ -0,0 +1,66 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2004, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) + * + * 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. + * + */ +#include <linux/module.h> +#include <asm/checksum.h> +#include <linux/io.h> + +/* + * libgcc functions - used internally by the compiler... + */ +extern int __c6xabi_divi(int dividend, int divisor); +EXPORT_SYMBOL(__c6xabi_divi); + +extern unsigned __c6xabi_divu(unsigned dividend, unsigned divisor); +EXPORT_SYMBOL(__c6xabi_divu); + +extern int __c6xabi_remi(int dividend, int divisor); +EXPORT_SYMBOL(__c6xabi_remi); + +extern unsigned __c6xabi_remu(unsigned dividend, unsigned divisor); +EXPORT_SYMBOL(__c6xabi_remu); + +extern int __c6xabi_divremi(int dividend, int divisor); +EXPORT_SYMBOL(__c6xabi_divremi); + +extern unsigned __c6xabi_divremu(unsigned dividend, unsigned divisor); +EXPORT_SYMBOL(__c6xabi_divremu); + +extern unsigned long long __c6xabi_mpyll(unsigned long long src1, + unsigned long long src2); +EXPORT_SYMBOL(__c6xabi_mpyll); + +extern long long __c6xabi_negll(long long src); +EXPORT_SYMBOL(__c6xabi_negll); + +extern unsigned long long __c6xabi_llshl(unsigned long long src1, uint src2); +EXPORT_SYMBOL(__c6xabi_llshl); + +extern long long __c6xabi_llshr(long long src1, uint src2); +EXPORT_SYMBOL(__c6xabi_llshr); + +extern unsigned long long __c6xabi_llshru(unsigned long long src1, uint src2); +EXPORT_SYMBOL(__c6xabi_llshru); + +extern void __c6xabi_strasgi(int *dst, const int *src, unsigned cnt); +EXPORT_SYMBOL(__c6xabi_strasgi); + +extern void __c6xabi_push_rts(void); +EXPORT_SYMBOL(__c6xabi_push_rts); + +extern void __c6xabi_pop_rts(void); +EXPORT_SYMBOL(__c6xabi_pop_rts); + +extern void __c6xabi_strasgi_64plus(int *dst, const int *src, unsigned cnt); +EXPORT_SYMBOL(__c6xabi_strasgi_64plus); + +/* lib functions */ +EXPORT_SYMBOL(memcpy); diff --git a/arch/c6x/kernel/devicetree.c b/arch/c6x/kernel/devicetree.c new file mode 100644 index 000000000000..bdb56f09d0ac --- /dev/null +++ b/arch/c6x/kernel/devicetree.c @@ -0,0 +1,53 @@ +/* + * Architecture specific OF callbacks. + * + * Copyright (C) 2011 Texas Instruments Incorporated + * Author: Mark Salter <msalter@redhat.com> + * + * 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. + * + */ +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/initrd.h> +#include <linux/memblock.h> + +void __init early_init_devtree(void *params) +{ + /* Setup flat device-tree pointer */ + initial_boot_params = params; + + /* Retrieve various informations from the /chosen node of the + * device-tree, including the platform type, initrd location and + * size and more ... + */ + of_scan_flat_dt(early_init_dt_scan_chosen, c6x_command_line); + + /* Scan memory nodes and rebuild MEMBLOCKs */ + of_scan_flat_dt(early_init_dt_scan_root, NULL); + of_scan_flat_dt(early_init_dt_scan_memory, NULL); +} + + +#ifdef CONFIG_BLK_DEV_INITRD +void __init early_init_dt_setup_initrd_arch(unsigned long start, + unsigned long end) +{ + initrd_start = (unsigned long)__va(start); + initrd_end = (unsigned long)__va(end); + initrd_below_start_ok = 1; +} +#endif + +void __init early_init_dt_add_memory_arch(u64 base, u64 size) +{ + c6x_add_memory(base, size); +} + +void * __init early_init_dt_alloc_memory_arch(u64 size, u64 align) +{ + return __va(memblock_alloc(size, align)); +} diff --git a/arch/c6x/kernel/dma.c b/arch/c6x/kernel/dma.c new file mode 100644 index 000000000000..ab7b12de144d --- /dev/null +++ b/arch/c6x/kernel/dma.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2011 Texas Instruments Incorporated + * Author: Mark Salter <msalter@redhat.com> + * + * 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. + */ +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/mm_types.h> +#include <linux/scatterlist.h> + +#include <asm/cacheflush.h> + +static void c6x_dma_sync(dma_addr_t handle, size_t size, + enum dma_data_direction dir) +{ + unsigned long paddr = handle; + + BUG_ON(!valid_dma_direction(dir)); + + switch (dir) { + case DMA_FROM_DEVICE: + L2_cache_block_invalidate(paddr, paddr + size); + break; + case DMA_TO_DEVICE: + L2_cache_block_writeback(paddr, paddr + size); + break; + case DMA_BIDIRECTIONAL: + L2_cache_block_writeback_invalidate(paddr, paddr + size); + break; + default: + break; + } +} + +dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size, + enum dma_data_direction dir) +{ + dma_addr_t addr = virt_to_phys(ptr); + + c6x_dma_sync(addr, size, dir); + + debug_dma_map_page(dev, virt_to_page(ptr), + (unsigned long)ptr & ~PAGE_MASK, size, + dir, addr, true); + return addr; +} +EXPORT_SYMBOL(dma_map_single); + + +void dma_unmap_single(struct device *dev, dma_addr_t handle, + size_t size, enum dma_data_direction dir) +{ + c6x_dma_sync(handle, size, dir); + + debug_dma_unmap_page(dev, handle, size, dir, true); +} +EXPORT_SYMBOL(dma_unmap_single); + + +int dma_map_sg(struct device *dev, struct scatterlist *sglist, + int nents, enum dma_data_direction dir) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sglist, sg, nents, i) + sg->dma_address = dma_map_single(dev, sg_virt(sg), sg->length, + dir); + + debug_dma_map_sg(dev, sglist, nents, nents, dir); + + return nents; +} +EXPORT_SYMBOL(dma_map_sg); + + +void dma_unmap_sg(struct device *dev, struct scatterlist *sglist, + int nents, enum dma_data_direction dir) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sglist, sg, nents, i) + dma_unmap_single(dev, sg_dma_address(sg), sg->length, dir); + + debug_dma_unmap_sg(dev, sglist, nents, dir); +} +EXPORT_SYMBOL(dma_unmap_sg); + +void dma_sync_single_for_cpu(struct device *dev, dma_addr_t handle, + size_t size, enum dma_data_direction dir) +{ + c6x_dma_sync(handle, size, dir); + + debug_dma_sync_single_for_cpu(dev, handle, size, dir); +} +EXPORT_SYMBOL(dma_sync_single_for_cpu); + + +void dma_sync_single_for_device(struct device *dev, dma_addr_t handle, + size_t size, enum dma_data_direction dir) +{ + c6x_dma_sync(handle, size, dir); + + debug_dma_sync_single_for_device(dev, handle, size, dir); +} +EXPORT_SYMBOL(dma_sync_single_for_device); + + +void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sglist, + int nents, enum dma_data_direction dir) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sglist, sg, nents, i) + dma_sync_single_for_cpu(dev, sg_dma_address(sg), + sg->length, dir); + + debug_dma_sync_sg_for_cpu(dev, sglist, nents, dir); +} +EXPORT_SYMBOL(dma_sync_sg_for_cpu); + + +void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sglist, + int nents, enum dma_data_direction dir) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sglist, sg, nents, i) + dma_sync_single_for_device(dev, sg_dma_address(sg), + sg->length, dir); + + debug_dma_sync_sg_for_device(dev, sglist, nents, dir); +} +EXPORT_SYMBOL(dma_sync_sg_for_device); + + +/* Number of entries preallocated for DMA-API debugging */ +#define PREALLOC_DMA_DEBUG_ENTRIES (1 << 16) + +static int __init dma_init(void) +{ + dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES); + + return 0; +} +fs_initcall(dma_init); diff --git a/arch/c6x/kernel/entry.S b/arch/c6x/kernel/entry.S new file mode 100644 index 000000000000..3e977ccda827 --- /dev/null +++ b/arch/c6x/kernel/entry.S @@ -0,0 +1,803 @@ +; +; Port on Texas Instruments TMS320C6x architecture +; +; Copyright (C) 2004-2011 Texas Instruments Incorporated +; Author: Aurelien Jacquiot (aurelien.jacquiot@virtuallogix.com) +; Updated for 2.6.34: Mark Salter <msalter@redhat.com> +; +; 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. +; + +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/thread_info.h> +#include <asm/asm-offsets.h> +#include <asm/unistd.h> +#include <asm/errno.h> + +; Registers naming +#define DP B14 +#define SP B15 + +#ifndef CONFIG_PREEMPT +#define resume_kernel restore_all +#endif + + .altmacro + + .macro MASK_INT reg + MVC .S2 CSR,reg + CLR .S2 reg,0,0,reg + MVC .S2 reg,CSR + .endm + + .macro UNMASK_INT reg + MVC .S2 CSR,reg + SET .S2 reg,0,0,reg + MVC .S2 reg,CSR + .endm + + .macro GET_THREAD_INFO reg + SHR .S1X SP,THREAD_SHIFT,reg + SHL .S1 reg,THREAD_SHIFT,reg + .endm + + ;; + ;; This defines the normal kernel pt_regs layout. + ;; + .macro SAVE_ALL __rp __tsr + STW .D2T2 B0,*SP--[2] ; save original B0 + MVKL .S2 current_ksp,B0 + MVKH .S2 current_ksp,B0 + LDW .D2T2 *B0,B1 ; KSP + + NOP 3 + STW .D2T2 B1,*+SP[1] ; save original B1 + XOR .D2 SP,B1,B0 ; (SP ^ KSP) + LDW .D2T2 *+SP[1],B1 ; restore B0/B1 + LDW .D2T2 *++SP[2],B0 + SHR .S2 B0,THREAD_SHIFT,B0 ; 0 if already using kstack + [B0] STDW .D2T2 SP:DP,*--B1[1] ; user: save user sp/dp kstack + [B0] MV .S2 B1,SP ; and switch to kstack +||[!B0] STDW .D2T2 SP:DP,*--SP[1] ; kernel: save on current stack + + SUBAW .D2 SP,2,SP + + ADD .D1X SP,-8,A15 + || STDW .D2T1 A15:A14,*SP--[16] ; save A15:A14 + + STDW .D2T2 B13:B12,*SP--[1] + || STDW .D1T1 A13:A12,*A15--[1] + || MVC .S2 __rp,B13 + + STDW .D2T2 B11:B10,*SP--[1] + || STDW .D1T1 A11:A10,*A15--[1] + || MVC .S2 CSR,B12 + + STDW .D2T2 B9:B8,*SP--[1] + || STDW .D1T1 A9:A8,*A15--[1] + || MVC .S2 RILC,B11 + STDW .D2T2 B7:B6,*SP--[1] + || STDW .D1T1 A7:A6,*A15--[1] + || MVC .S2 ILC,B10 + + STDW .D2T2 B5:B4,*SP--[1] + || STDW .D1T1 A5:A4,*A15--[1] + + STDW .D2T2 B3:B2,*SP--[1] + || STDW .D1T1 A3:A2,*A15--[1] + || MVC .S2 __tsr,B5 + + STDW .D2T2 B1:B0,*SP--[1] + || STDW .D1T1 A1:A0,*A15--[1] + || MV .S1X B5,A5 + + STDW .D2T2 B31:B30,*SP--[1] + || STDW .D1T1 A31:A30,*A15--[1] + STDW .D2T2 B29:B28,*SP--[1] + || STDW .D1T1 A29:A28,*A15--[1] + STDW .D2T2 B27:B26,*SP--[1] + || STDW .D1T1 A27:A26,*A15--[1] + STDW .D2T2 B25:B24,*SP--[1] + || STDW .D1T1 A25:A24,*A15--[1] + STDW .D2T2 B23:B22,*SP--[1] + || STDW .D1T1 A23:A22,*A15--[1] + STDW .D2T2 B21:B20,*SP--[1] + || STDW .D1T1 A21:A20,*A15--[1] + STDW .D2T2 B19:B18,*SP--[1] + || STDW .D1T1 A19:A18,*A15--[1] + STDW .D2T2 B17:B16,*SP--[1] + || STDW .D1T1 A17:A16,*A15--[1] + + STDW .D2T2 B13:B12,*SP--[1] ; save PC and CSR + + STDW .D2T2 B11:B10,*SP--[1] ; save RILC and ILC + STDW .D2T1 A5:A4,*SP--[1] ; save TSR and orig A4 + + ;; We left an unused word on the stack just above pt_regs. + ;; It is used to save whether or not this frame is due to + ;; a syscall. It is cleared here, but the syscall handler + ;; sets it to a non-zero value. + MVK .L2 0,B1 + STW .D2T2 B1,*+SP(REGS__END+8) ; clear syscall flag + .endm + + .macro RESTORE_ALL __rp __tsr + LDDW .D2T2 *++SP[1],B9:B8 ; get TSR (B9) + LDDW .D2T2 *++SP[1],B11:B10 ; get RILC (B11) and ILC (B10) + LDDW .D2T2 *++SP[1],B13:B12 ; get PC (B13) and CSR (B12) + + ADDAW .D1X SP,30,A15 + + LDDW .D1T1 *++A15[1],A17:A16 + || LDDW .D2T2 *++SP[1],B17:B16 + LDDW .D1T1 *++A15[1],A19:A18 + || LDDW .D2T2 *++SP[1],B19:B18 + LDDW .D1T1 *++A15[1],A21:A20 + || LDDW .D2T2 *++SP[1],B21:B20 + LDDW .D1T1 *++A15[1],A23:A22 + || LDDW .D2T2 *++SP[1],B23:B22 + LDDW .D1T1 *++A15[1],A25:A24 + || LDDW .D2T2 *++SP[1],B25:B24 + LDDW .D1T1 *++A15[1],A27:A26 + || LDDW .D2T2 *++SP[1],B27:B26 + LDDW .D1T1 *++A15[1],A29:A28 + || LDDW .D2T2 *++SP[1],B29:B28 + LDDW .D1T1 *++A15[1],A31:A30 + || LDDW .D2T2 *++SP[1],B31:B30 + + LDDW .D1T1 *++A15[1],A1:A0 + || LDDW .D2T2 *++SP[1],B1:B0 + + LDDW .D1T1 *++A15[1],A3:A2 + || LDDW .D2T2 *++SP[1],B3:B2 + || MVC .S2 B9,__tsr + LDDW .D1T1 *++A15[1],A5:A4 + || LDDW .D2T2 *++SP[1],B5:B4 + || MVC .S2 B11,RILC + LDDW .D1T1 *++A15[1],A7:A6 + || LDDW .D2T2 *++SP[1],B7:B6 + || MVC .S2 B10,ILC + + LDDW .D1T1 *++A15[1],A9:A8 + || LDDW .D2T2 *++SP[1],B9:B8 + || MVC .S2 B13,__rp + + LDDW .D1T1 *++A15[1],A11:A10 + || LDDW .D2T2 *++SP[1],B11:B10 + || MVC .S2 B12,CSR + + LDDW .D1T1 *++A15[1],A13:A12 + || LDDW .D2T2 *++SP[1],B13:B12 + + MV .D2X A15,SP + || MVKL .S1 current_ksp,A15 + MVKH .S1 current_ksp,A15 + || ADDAW .D1X SP,6,A14 + STW .D1T1 A14,*A15 ; save kernel stack pointer + + LDDW .D2T1 *++SP[1],A15:A14 + + B .S2 __rp ; return from interruption + LDDW .D2T2 *+SP[1],SP:DP + NOP 4 + .endm + + .section .text + + ;; + ;; Jump to schedule() then return to ret_from_exception + ;; +_reschedule: +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 schedule,A0 + MVKH .S1 schedule,A0 + B .S2X A0 +#else + B .S1 schedule +#endif + ADDKPC .S2 ret_from_exception,B3,4 + + ;; + ;; Called before syscall handler when process is being debugged + ;; +tracesys_on: +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 syscall_trace_entry,A0 + MVKH .S1 syscall_trace_entry,A0 + B .S2X A0 +#else + B .S1 syscall_trace_entry +#endif + ADDKPC .S2 ret_from_syscall_trace,B3,3 + ADD .S1X 8,SP,A4 + +ret_from_syscall_trace: + ;; tracing returns (possibly new) syscall number + MV .D2X A4,B0 + || MVK .S2 __NR_syscalls,B1 + CMPLTU .L2 B0,B1,B1 + + [!B1] BNOP .S2 ret_from_syscall_function,5 + || MVK .S1 -ENOSYS,A4 + + ;; reload syscall args from (possibly modified) stack frame + ;; and get syscall handler addr from sys_call_table: + LDW .D2T2 *+SP(REGS_B4+8),B4 + || MVKL .S2 sys_call_table,B1 + LDW .D2T1 *+SP(REGS_A6+8),A6 + || MVKH .S2 sys_call_table,B1 + LDW .D2T2 *+B1[B0],B0 + || MVKL .S2 ret_from_syscall_function,B3 + LDW .D2T2 *+SP(REGS_B6+8),B6 + || MVKH .S2 ret_from_syscall_function,B3 + LDW .D2T1 *+SP(REGS_A8+8),A8 + LDW .D2T2 *+SP(REGS_B8+8),B8 + NOP + ; B0 = sys_call_table[__NR_*] + BNOP .S2 B0,5 ; branch to syscall handler + || LDW .D2T1 *+SP(REGS_ORIG_A4+8),A4 + +syscall_exit_work: + AND .D1 _TIF_SYSCALL_TRACE,A2,A0 + [!A0] BNOP .S1 work_pending,5 + [A0] B .S2 syscall_trace_exit + ADDKPC .S2 resume_userspace,B3,1 + MVC .S2 CSR,B1 + SET .S2 B1,0,0,B1 + MVC .S2 B1,CSR ; enable ints + +work_pending: + AND .D1 _TIF_NEED_RESCHED,A2,A0 + [!A0] BNOP .S1 work_notifysig,5 + +work_resched: +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 schedule,A1 + MVKH .S1 schedule,A1 + B .S2X A1 +#else + B .S2 schedule +#endif + ADDKPC .S2 work_rescheduled,B3,4 +work_rescheduled: + ;; make sure we don't miss an interrupt setting need_resched or + ;; sigpending between sampling and the rti + MASK_INT B2 + GET_THREAD_INFO A12 + LDW .D1T1 *+A12(THREAD_INFO_FLAGS),A2 + MVK .S1 _TIF_WORK_MASK,A1 + MVK .S1 _TIF_NEED_RESCHED,A3 + NOP 2 + AND .D1 A1,A2,A0 + || AND .S1 A3,A2,A1 + [!A0] BNOP .S1 restore_all,5 + [A1] BNOP .S1 work_resched,5 + +work_notifysig: + B .S2 do_notify_resume + LDW .D2T1 *+SP(REGS__END+8),A6 ; syscall flag + ADDKPC .S2 resume_userspace,B3,1 + ADD .S1X 8,SP,A4 ; pt_regs pointer is first arg + MV .D2X A2,B4 ; thread_info flags is second arg + + ;; + ;; On C64x+, the return way from exception and interrupt + ;; is a little bit different + ;; +ENTRY(ret_from_exception) +#ifdef CONFIG_PREEMPT + MASK_INT B2 +#endif + +ENTRY(ret_from_interrupt) + ;; + ;; Check if we are comming from user mode. + ;; + LDW .D2T2 *+SP(REGS_TSR+8),B0 + MVK .S2 0x40,B1 + NOP 3 + AND .D2 B0,B1,B0 + [!B0] BNOP .S2 resume_kernel,5 + +resume_userspace: + ;; make sure we don't miss an interrupt setting need_resched or + ;; sigpending between sampling and the rti + MASK_INT B2 + GET_THREAD_INFO A12 + LDW .D1T1 *+A12(THREAD_INFO_FLAGS),A2 + MVK .S1 _TIF_WORK_MASK,A1 + MVK .S1 _TIF_NEED_RESCHED,A3 + NOP 2 + AND .D1 A1,A2,A0 + [A0] BNOP .S1 work_pending,5 + BNOP .S1 restore_all,5 + + ;; + ;; System call handling + ;; B0 = syscall number (in sys_call_table) + ;; A4,B4,A6,B6,A8,B8 = arguments of the syscall function + ;; A4 is the return value register + ;; +system_call_saved: + MVK .L2 1,B2 + STW .D2T2 B2,*+SP(REGS__END+8) ; set syscall flag + MVC .S2 B2,ECR ; ack the software exception + + UNMASK_INT B2 ; re-enable global IT + +system_call_saved_noack: + ;; Check system call number + MVK .S2 __NR_syscalls,B1 +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKL .S1 sys_ni_syscall,A0 +#endif + CMPLTU .L2 B0,B1,B1 +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKH .S1 sys_ni_syscall,A0 +#endif + + ;; Check for ptrace + GET_THREAD_INFO A12 + +#ifdef CONFIG_C6X_BIG_KERNEL + [!B1] B .S2X A0 +#else + [!B1] B .S2 sys_ni_syscall +#endif + [!B1] ADDKPC .S2 ret_from_syscall_function,B3,4 + + ;; Get syscall handler addr from sys_call_table + ;; call tracesys_on or call syscall handler + LDW .D1T1 *+A12(THREAD_INFO_FLAGS),A2 + || MVKL .S2 sys_call_table,B1 + MVKH .S2 sys_call_table,B1 + LDW .D2T2 *+B1[B0],B0 + NOP 2 + ; A2 = thread_info flags + AND .D1 _TIF_SYSCALL_TRACE,A2,A2 + [A2] BNOP .S1 tracesys_on,5 + ;; B0 = _sys_call_table[__NR_*] + B .S2 B0 + ADDKPC .S2 ret_from_syscall_function,B3,4 + +ret_from_syscall_function: + STW .D2T1 A4,*+SP(REGS_A4+8) ; save return value in A4 + ; original A4 is in orig_A4 +syscall_exit: + ;; make sure we don't miss an interrupt setting need_resched or + ;; sigpending between sampling and the rti + MASK_INT B2 + LDW .D1T1 *+A12(THREAD_INFO_FLAGS),A2 + MVK .S1 _TIF_ALLWORK_MASK,A1 + NOP 3 + AND .D1 A1,A2,A2 ; check for work to do + [A2] BNOP .S1 syscall_exit_work,5 + +restore_all: + RESTORE_ALL NRP,NTSR + + ;; + ;; After a fork we jump here directly from resume, + ;; so that A4 contains the previous task structure. + ;; +ENTRY(ret_from_fork) +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 schedule_tail,A0 + MVKH .S1 schedule_tail,A0 + B .S2X A0 +#else + B .S2 schedule_tail +#endif + ADDKPC .S2 ret_from_fork_2,B3,4 +ret_from_fork_2: + ;; return 0 in A4 for child process + GET_THREAD_INFO A12 + BNOP .S2 syscall_exit,3 + MVK .L2 0,B0 + STW .D2T2 B0,*+SP(REGS_A4+8) +ENDPROC(ret_from_fork) + + ;; + ;; These are the interrupt handlers, responsible for calling __do_IRQ() + ;; int6 is used for syscalls (see _system_call entry) + ;; + .macro SAVE_ALL_INT + SAVE_ALL IRP,ITSR + .endm + + .macro CALL_INT int +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 c6x_do_IRQ,A0 + MVKH .S1 c6x_do_IRQ,A0 + BNOP .S2X A0,1 + MVK .S1 int,A4 + ADDAW .D2 SP,2,B4 + MVKL .S2 ret_from_interrupt,B3 + MVKH .S2 ret_from_interrupt,B3 +#else + CALLP .S2 c6x_do_IRQ,B3 + || MVK .S1 int,A4 + || ADDAW .D2 SP,2,B4 + B .S1 ret_from_interrupt + NOP 5 +#endif + .endm + +ENTRY(_int4_handler) + SAVE_ALL_INT + CALL_INT 4 +ENDPROC(_int4_handler) + +ENTRY(_int5_handler) + SAVE_ALL_INT + CALL_INT 5 +ENDPROC(_int5_handler) + +ENTRY(_int6_handler) + SAVE_ALL_INT + CALL_INT 6 +ENDPROC(_int6_handler) + +ENTRY(_int7_handler) + SAVE_ALL_INT + CALL_INT 7 +ENDPROC(_int7_handler) + +ENTRY(_int8_handler) + SAVE_ALL_INT + CALL_INT 8 +ENDPROC(_int8_handler) + +ENTRY(_int9_handler) + SAVE_ALL_INT + CALL_INT 9 +ENDPROC(_int9_handler) + +ENTRY(_int10_handler) + SAVE_ALL_INT + CALL_INT 10 +ENDPROC(_int10_handler) + +ENTRY(_int11_handler) + SAVE_ALL_INT + CALL_INT 11 +ENDPROC(_int11_handler) + +ENTRY(_int12_handler) + SAVE_ALL_INT + CALL_INT 12 +ENDPROC(_int12_handler) + +ENTRY(_int13_handler) + SAVE_ALL_INT + CALL_INT 13 +ENDPROC(_int13_handler) + +ENTRY(_int14_handler) + SAVE_ALL_INT + CALL_INT 14 +ENDPROC(_int14_handler) + +ENTRY(_int15_handler) + SAVE_ALL_INT + CALL_INT 15 +ENDPROC(_int15_handler) + + ;; + ;; Handler for uninitialized and spurious interrupts + ;; +ENTRY(_bad_interrupt) + B .S2 IRP + NOP 5 +ENDPROC(_bad_interrupt) + + ;; + ;; Entry for NMI/exceptions/syscall + ;; +ENTRY(_nmi_handler) + SAVE_ALL NRP,NTSR + + MVC .S2 EFR,B2 + CMPEQ .L2 1,B2,B2 + || MVC .S2 TSR,B1 + CLR .S2 B1,10,10,B1 + MVC .S2 B1,TSR +#ifdef CONFIG_C6X_BIG_KERNEL + [!B2] MVKL .S1 process_exception,A0 + [!B2] MVKH .S1 process_exception,A0 + [!B2] B .S2X A0 +#else + [!B2] B .S2 process_exception +#endif + [B2] B .S2 system_call_saved + [!B2] ADDAW .D2 SP,2,B1 + [!B2] MV .D1X B1,A4 + ADDKPC .S2 ret_from_trap,B3,2 + +ret_from_trap: + MV .D2X A4,B0 + [!B0] BNOP .S2 ret_from_exception,5 + +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S2 system_call_saved_noack,B3 + MVKH .S2 system_call_saved_noack,B3 +#endif + LDW .D2T2 *+SP(REGS_B0+8),B0 + LDW .D2T1 *+SP(REGS_A4+8),A4 + LDW .D2T2 *+SP(REGS_B4+8),B4 + LDW .D2T1 *+SP(REGS_A6+8),A6 + LDW .D2T2 *+SP(REGS_B6+8),B6 + LDW .D2T1 *+SP(REGS_A8+8),A8 +#ifdef CONFIG_C6X_BIG_KERNEL + || B .S2 B3 +#else + || B .S2 system_call_saved_noack +#endif + LDW .D2T2 *+SP(REGS_B8+8),B8 + NOP 4 +ENDPROC(_nmi_handler) + + ;; + ;; Jump to schedule() then return to ret_from_isr + ;; +#ifdef CONFIG_PREEMPT +resume_kernel: + GET_THREAD_INFO A12 + LDW .D1T1 *+A12(THREAD_INFO_PREEMPT_COUNT),A1 + NOP 4 + [A1] BNOP .S2 restore_all,5 + +preempt_schedule: + GET_THREAD_INFO A2 + LDW .D1T1 *+A2(THREAD_INFO_FLAGS),A1 +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S2 preempt_schedule_irq,B0 + MVKH .S2 preempt_schedule_irq,B0 + NOP 2 +#else + NOP 4 +#endif + AND .D1 _TIF_NEED_RESCHED,A1,A1 + [!A1] BNOP .S2 restore_all,5 +#ifdef CONFIG_C6X_BIG_KERNEL + B .S2 B0 +#else + B .S2 preempt_schedule_irq +#endif + ADDKPC .S2 preempt_schedule,B3,4 +#endif /* CONFIG_PREEMPT */ + +ENTRY(enable_exception) + DINT + MVC .S2 TSR,B0 + MVC .S2 B3,NRP + MVK .L2 0xc,B1 + OR .D2 B0,B1,B0 + MVC .S2 B0,TSR ; Set GEE and XEN in TSR + B .S2 NRP + NOP 5 +ENDPROC(enable_exception) + +ENTRY(sys_sigaltstack) +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 do_sigaltstack,A0 ; branch to do_sigaltstack + MVKH .S1 do_sigaltstack,A0 + B .S2X A0 +#else + B .S2 do_sigaltstack +#endif + LDW .D2T1 *+SP(REGS_SP+8),A6 + NOP 4 +ENDPROC(sys_sigaltstack) + + ;; kernel_execve +ENTRY(kernel_execve) + MVK .S2 __NR_execve,B0 + SWE + BNOP .S2 B3,5 +ENDPROC(kernel_execve) + + ;; + ;; Special system calls + ;; return address is in B3 + ;; +ENTRY(sys_clone) + ADD .D1X SP,8,A4 +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKL .S1 sys_c6x_clone,A0 + MVKH .S1 sys_c6x_clone,A0 + BNOP .S2X A0,5 +#else + || B .S2 sys_c6x_clone + NOP 5 +#endif +ENDPROC(sys_clone) + +ENTRY(sys_rt_sigreturn) + ADD .D1X SP,8,A4 +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKL .S1 do_rt_sigreturn,A0 + MVKH .S1 do_rt_sigreturn,A0 + BNOP .S2X A0,5 +#else + || B .S2 do_rt_sigreturn + NOP 5 +#endif +ENDPROC(sys_rt_sigreturn) + +ENTRY(sys_execve) + ADDAW .D2 SP,2,B6 ; put regs addr in 4th parameter + ; & adjust regs stack addr + LDW .D2T2 *+SP(REGS_B4+8),B4 + + ;; c6x_execve(char *name, char **argv, + ;; char **envp, struct pt_regs *regs) +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKL .S1 sys_c6x_execve,A0 + MVKH .S1 sys_c6x_execve,A0 + B .S2X A0 +#else + || B .S2 sys_c6x_execve +#endif + STW .D2T2 B3,*SP--[2] + ADDKPC .S2 ret_from_c6x_execve,B3,3 + +ret_from_c6x_execve: + LDW .D2T2 *++SP[2],B3 + NOP 4 + BNOP .S2 B3,5 +ENDPROC(sys_execve) + +ENTRY(sys_pread_c6x) + MV .D2X A8,B7 +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKL .S1 sys_pread64,A0 + MVKH .S1 sys_pread64,A0 + BNOP .S2X A0,5 +#else + || B .S2 sys_pread64 + NOP 5 +#endif +ENDPROC(sys_pread_c6x) + +ENTRY(sys_pwrite_c6x) + MV .D2X A8,B7 +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKL .S1 sys_pwrite64,A0 + MVKH .S1 sys_pwrite64,A0 + BNOP .S2X A0,5 +#else + || B .S2 sys_pwrite64 + NOP 5 +#endif +ENDPROC(sys_pwrite_c6x) + +;; On Entry +;; A4 - path +;; B4 - offset_lo (LE), offset_hi (BE) +;; A6 - offset_lo (BE), offset_hi (LE) +ENTRY(sys_truncate64_c6x) +#ifdef CONFIG_CPU_BIG_ENDIAN + MV .S2 B4,B5 + MV .D2X A6,B4 +#else + MV .D2X A6,B5 +#endif +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKL .S1 sys_truncate64,A0 + MVKH .S1 sys_truncate64,A0 + BNOP .S2X A0,5 +#else + || B .S2 sys_truncate64 + NOP 5 +#endif +ENDPROC(sys_truncate64_c6x) + +;; On Entry +;; A4 - fd +;; B4 - offset_lo (LE), offset_hi (BE) +;; A6 - offset_lo (BE), offset_hi (LE) +ENTRY(sys_ftruncate64_c6x) +#ifdef CONFIG_CPU_BIG_ENDIAN + MV .S2 B4,B5 + MV .D2X A6,B4 +#else + MV .D2X A6,B5 +#endif +#ifdef CONFIG_C6X_BIG_KERNEL + || MVKL .S1 sys_ftruncate64,A0 + MVKH .S1 sys_ftruncate64,A0 + BNOP .S2X A0,5 +#else + || B .S2 sys_ftruncate64 + NOP 5 +#endif +ENDPROC(sys_ftruncate64_c6x) + +#ifdef __ARCH_WANT_SYSCALL_OFF_T +;; On Entry +;; A4 - fd +;; B4 - offset_lo (LE), offset_hi (BE) +;; A6 - offset_lo (BE), offset_hi (LE) +;; B6 - len +;; A8 - advice +ENTRY(sys_fadvise64_c6x) +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 sys_fadvise64,A0 + MVKH .S1 sys_fadvise64,A0 + BNOP .S2X A0,2 +#else + B .S2 sys_fadvise64 + NOP 2 +#endif +#ifdef CONFIG_CPU_BIG_ENDIAN + MV .L2 B4,B5 + || MV .D2X A6,B4 +#else + MV .D2X A6,B5 +#endif + MV .D1X B6,A6 + MV .D2X A8,B6 +#endif +ENDPROC(sys_fadvise64_c6x) + +;; On Entry +;; A4 - fd +;; B4 - offset_lo (LE), offset_hi (BE) +;; A6 - offset_lo (BE), offset_hi (LE) +;; B6 - len_lo (LE), len_hi (BE) +;; A8 - len_lo (BE), len_hi (LE) +;; B8 - advice +ENTRY(sys_fadvise64_64_c6x) +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 sys_fadvise64_64,A0 + MVKH .S1 sys_fadvise64_64,A0 + BNOP .S2X A0,2 +#else + B .S2 sys_fadvise64_64 + NOP 2 +#endif +#ifdef CONFIG_CPU_BIG_ENDIAN + MV .L2 B4,B5 + || MV .D2X A6,B4 + MV .L1 A8,A6 + || MV .D1X B6,A7 +#else + MV .D2X A6,B5 + MV .L1 A8,A7 + || MV .D1X B6,A6 +#endif + MV .L2 B8,B6 +ENDPROC(sys_fadvise64_64_c6x) + +;; On Entry +;; A4 - fd +;; B4 - mode +;; A6 - offset_hi +;; B6 - offset_lo +;; A8 - len_hi +;; B8 - len_lo +ENTRY(sys_fallocate_c6x) +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 sys_fallocate,A0 + MVKH .S1 sys_fallocate,A0 + BNOP .S2X A0,1 +#else + B .S2 sys_fallocate + NOP +#endif + MV .D1 A6,A7 + MV .D1X B6,A6 + MV .D2X A8,B7 + MV .D2 B8,B6 +ENDPROC(sys_fallocate_c6x) + + ;; put this in .neardata for faster access when using DSBT mode + .section .neardata,"aw",@progbits + .global current_ksp + .hidden current_ksp +current_ksp: + .word init_thread_union + THREAD_START_SP diff --git a/arch/c6x/kernel/head.S b/arch/c6x/kernel/head.S new file mode 100644 index 000000000000..133eab6edf6b --- /dev/null +++ b/arch/c6x/kernel/head.S @@ -0,0 +1,84 @@ +; +; Port on Texas Instruments TMS320C6x architecture +; +; Copyright (C) 2004, 2009, 2010, 2011 Texas Instruments Incorporated +; Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) +; +; 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. +; +#include <linux/linkage.h> +#include <linux/of_fdt.h> +#include <asm/asm-offsets.h> + + __HEAD +ENTRY(_c_int00) + ;; Save magic and pointer + MV .S1 A4,A10 + MV .S2 B4,B10 + MVKL .S2 __bss_start,B5 + MVKH .S2 __bss_start,B5 + MVKL .S2 __bss_stop,B6 + MVKH .S2 __bss_stop,B6 + SUB .L2 B6,B5,B6 ; bss size + + ;; Set the stack pointer + MVKL .S2 current_ksp,B0 + MVKH .S2 current_ksp,B0 + LDW .D2T2 *B0,B15 + + ;; clear bss + SHR .S2 B6,3,B0 ; number of dwords to clear + ZERO .L2 B13 + ZERO .L2 B12 +bss_loop: + BDEC .S2 bss_loop,B0 + NOP 3 + CMPLT .L2 B0,0,B1 + [!B1] STDW .D2T2 B13:B12,*B5++[1] + + NOP 4 + AND .D2 ~7,B15,B15 + + ;; Clear GIE and PGIE + MVC .S2 CSR,B2 + CLR .S2 B2,0,1,B2 + MVC .S2 B2,CSR + MVC .S2 TSR,B2 + CLR .S2 B2,0,1,B2 + MVC .S2 B2,TSR + MVC .S2 ITSR,B2 + CLR .S2 B2,0,1,B2 + MVC .S2 B2,ITSR + MVC .S2 NTSR,B2 + CLR .S2 B2,0,1,B2 + MVC .S2 B2,NTSR + + ;; pass DTB pointer to machine_init (or zero if none) + MVKL .S1 OF_DT_HEADER,A0 + MVKH .S1 OF_DT_HEADER,A0 + CMPEQ .L1 A10,A0,A0 + [A0] MV .S1X B10,A4 + [!A0] MVK .S1 0,A4 + +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 machine_init,A0 + MVKH .S1 machine_init,A0 + B .S2X A0 + ADDKPC .S2 0f,B3,4 +0: +#else + CALLP .S2 machine_init,B3 +#endif + + ;; Jump to Linux init +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 start_kernel,A0 + MVKH .S1 start_kernel,A0 + B .S2X A0 +#else + B .S2 start_kernel +#endif + NOP 5 +L1: BNOP .S2 L1,5 diff --git a/arch/c6x/kernel/irq.c b/arch/c6x/kernel/irq.c new file mode 100644 index 000000000000..0929e4b2b244 --- /dev/null +++ b/arch/c6x/kernel/irq.c @@ -0,0 +1,728 @@ +/* + * Copyright (C) 2011 Texas Instruments Incorporated + * + * This borrows heavily from powerpc version, which is: + * + * Derived from arch/i386/kernel/irq.c + * Copyright (C) 1992 Linus Torvalds + * Adapted from arch/i386 by Gary Thomas + * Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org) + * Updated and modified by Cort Dougan <cort@fsmlabs.com> + * Copyright (C) 1996-2001 Cort Dougan + * Adapted for Power Macintosh by Paul Mackerras + * Copyright (C) 1996 Paul Mackerras (paulus@cs.anu.edu.au) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/radix-tree.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> + +#include <asm/megamod-pic.h> + +unsigned long irq_err_count; + +static DEFINE_RAW_SPINLOCK(core_irq_lock); + +static void mask_core_irq(struct irq_data *data) +{ + unsigned int prio = data->irq; + + BUG_ON(prio < 4 || prio >= NR_PRIORITY_IRQS); + + raw_spin_lock(&core_irq_lock); + and_creg(IER, ~(1 << prio)); + raw_spin_unlock(&core_irq_lock); +} + +static void unmask_core_irq(struct irq_data *data) +{ + unsigned int prio = data->irq; + + raw_spin_lock(&core_irq_lock); + or_creg(IER, 1 << prio); + raw_spin_unlock(&core_irq_lock); +} + +static struct irq_chip core_chip = { + .name = "core", + .irq_mask = mask_core_irq, + .irq_unmask = unmask_core_irq, +}; + +asmlinkage void c6x_do_IRQ(unsigned int prio, struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + irq_enter(); + + BUG_ON(prio < 4 || prio >= NR_PRIORITY_IRQS); + + generic_handle_irq(prio); + + irq_exit(); + + set_irq_regs(old_regs); +} + +static struct irq_host *core_host; + +static int core_host_map(struct irq_host *h, unsigned int virq, + irq_hw_number_t hw) +{ + if (hw < 4 || hw >= NR_PRIORITY_IRQS) + return -EINVAL; + + irq_set_status_flags(virq, IRQ_LEVEL); + irq_set_chip_and_handler(virq, &core_chip, handle_level_irq); + return 0; +} + +static struct irq_host_ops core_host_ops = { + .map = core_host_map, +}; + +void __init init_IRQ(void) +{ + struct device_node *np; + + /* Mask all priority IRQs */ + and_creg(IER, ~0xfff0); + + np = of_find_compatible_node(NULL, NULL, "ti,c64x+core-pic"); + if (np != NULL) { + /* create the core host */ + core_host = irq_alloc_host(np, IRQ_HOST_MAP_PRIORITY, 0, + &core_host_ops, 0); + if (core_host) + irq_set_default_host(core_host); + of_node_put(np); + } + + printk(KERN_INFO "Core interrupt controller initialized\n"); + + /* now we're ready for other SoC controllers */ + megamod_pic_init(); + + /* Clear all general IRQ flags */ + set_creg(ICR, 0xfff0); +} + +void ack_bad_irq(int irq) +{ + printk(KERN_ERR "IRQ: spurious interrupt %d\n", irq); + irq_err_count++; +} + +int arch_show_interrupts(struct seq_file *p, int prec) +{ + seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count); + return 0; +} + +/* + * IRQ controller and virtual interrupts + */ + +/* The main irq map itself is an array of NR_IRQ entries containing the + * associate host and irq number. An entry with a host of NULL is free. + * An entry can be allocated if it's free, the allocator always then sets + * hwirq first to the host's invalid irq number and then fills ops. + */ +struct irq_map_entry { + irq_hw_number_t hwirq; + struct irq_host *host; +}; + +static LIST_HEAD(irq_hosts); +static DEFINE_RAW_SPINLOCK(irq_big_lock); +static DEFINE_MUTEX(revmap_trees_mutex); +static struct irq_map_entry irq_map[NR_IRQS]; +static unsigned int irq_virq_count = NR_IRQS; +static struct irq_host *irq_default_host; + +irq_hw_number_t irqd_to_hwirq(struct irq_data *d) +{ + return irq_map[d->irq].hwirq; +} +EXPORT_SYMBOL_GPL(irqd_to_hwirq); + +irq_hw_number_t virq_to_hw(unsigned int virq) +{ + return irq_map[virq].hwirq; +} +EXPORT_SYMBOL_GPL(virq_to_hw); + +bool virq_is_host(unsigned int virq, struct irq_host *host) +{ + return irq_map[virq].host == host; +} +EXPORT_SYMBOL_GPL(virq_is_host); + +static int default_irq_host_match(struct irq_host *h, struct device_node *np) +{ + return h->of_node != NULL && h->of_node == np; +} + +struct irq_host *irq_alloc_host(struct device_node *of_node, + unsigned int revmap_type, + unsigned int revmap_arg, + struct irq_host_ops *ops, + irq_hw_number_t inval_irq) +{ + struct irq_host *host; + unsigned int size = sizeof(struct irq_host); + unsigned int i; + unsigned int *rmap; + unsigned long flags; + + /* Allocate structure and revmap table if using linear mapping */ + if (revmap_type == IRQ_HOST_MAP_LINEAR) + size += revmap_arg * sizeof(unsigned int); + host = kzalloc(size, GFP_KERNEL); + if (host == NULL) + return NULL; + + /* Fill structure */ + host->revmap_type = revmap_type; + host->inval_irq = inval_irq; + host->ops = ops; + host->of_node = of_node_get(of_node); + + if (host->ops->match == NULL) + host->ops->match = default_irq_host_match; + + raw_spin_lock_irqsave(&irq_big_lock, flags); + + /* Check for the priority controller. */ + if (revmap_type == IRQ_HOST_MAP_PRIORITY) { + if (irq_map[0].host != NULL) { + raw_spin_unlock_irqrestore(&irq_big_lock, flags); + of_node_put(host->of_node); + kfree(host); + return NULL; + } + irq_map[0].host = host; + } + + list_add(&host->link, &irq_hosts); + raw_spin_unlock_irqrestore(&irq_big_lock, flags); + + /* Additional setups per revmap type */ + switch (revmap_type) { + case IRQ_HOST_MAP_PRIORITY: + /* 0 is always the invalid number for priority */ + host->inval_irq = 0; + /* setup us as the host for all priority interrupts */ + for (i = 1; i < NR_PRIORITY_IRQS; i++) { + irq_map[i].hwirq = i; + smp_wmb(); + irq_map[i].host = host; + smp_wmb(); + + ops->map(host, i, i); + } + break; + case IRQ_HOST_MAP_LINEAR: + rmap = (unsigned int *)(host + 1); + for (i = 0; i < revmap_arg; i++) + rmap[i] = NO_IRQ; + host->revmap_data.linear.size = revmap_arg; + smp_wmb(); + host->revmap_data.linear.revmap = rmap; + break; + case IRQ_HOST_MAP_TREE: + INIT_RADIX_TREE(&host->revmap_data.tree, GFP_KERNEL); + break; + default: + break; + } + + pr_debug("irq: Allocated host of type %d @0x%p\n", revmap_type, host); + + return host; +} + +struct irq_host *irq_find_host(struct device_node *node) +{ + struct irq_host *h, *found = NULL; + unsigned long flags; + + /* We might want to match the legacy controller last since + * it might potentially be set to match all interrupts in + * the absence of a device node. This isn't a problem so far + * yet though... + */ + raw_spin_lock_irqsave(&irq_big_lock, flags); + list_for_each_entry(h, &irq_hosts, link) + if (h->ops->match(h, node)) { + found = h; + break; + } + raw_spin_unlock_irqrestore(&irq_big_lock, flags); + return found; +} +EXPORT_SYMBOL_GPL(irq_find_host); + +void irq_set_default_host(struct irq_host *host) +{ + pr_debug("irq: Default host set to @0x%p\n", host); + + irq_default_host = host; +} + +void irq_set_virq_count(unsigned int count) +{ + pr_debug("irq: Trying to set virq count to %d\n", count); + + BUG_ON(count < NR_PRIORITY_IRQS); + if (count < NR_IRQS) + irq_virq_count = count; +} + +static int irq_setup_virq(struct irq_host *host, unsigned int virq, + irq_hw_number_t hwirq) +{ + int res; + + res = irq_alloc_desc_at(virq, 0); + if (res != virq) { + pr_debug("irq: -> allocating desc failed\n"); + goto error; + } + + /* map it */ + smp_wmb(); + irq_map[virq].hwirq = hwirq; + smp_mb(); + + if (host->ops->map(host, virq, hwirq)) { + pr_debug("irq: -> mapping failed, freeing\n"); + goto errdesc; + } + + irq_clear_status_flags(virq, IRQ_NOREQUEST); + + return 0; + +errdesc: + irq_free_descs(virq, 1); +error: + irq_free_virt(virq, 1); + return -1; +} + +unsigned int irq_create_direct_mapping(struct irq_host *host) +{ + unsigned int virq; + + if (host == NULL) + host = irq_default_host; + + BUG_ON(host == NULL); + WARN_ON(host->revmap_type != IRQ_HOST_MAP_NOMAP); + + virq = irq_alloc_virt(host, 1, 0); + if (virq == NO_IRQ) { + pr_debug("irq: create_direct virq allocation failed\n"); + return NO_IRQ; + } + + pr_debug("irq: create_direct obtained virq %d\n", virq); + + if (irq_setup_virq(host, virq, virq)) + return NO_IRQ; + + return virq; +} + +unsigned int irq_create_mapping(struct irq_host *host, + irq_hw_number_t hwirq) +{ + unsigned int virq, hint; + + pr_debug("irq: irq_create_mapping(0x%p, 0x%lx)\n", host, hwirq); + + /* Look for default host if nececssary */ + if (host == NULL) + host = irq_default_host; + if (host == NULL) { + printk(KERN_WARNING "irq_create_mapping called for" + " NULL host, hwirq=%lx\n", hwirq); + WARN_ON(1); + return NO_IRQ; + } + pr_debug("irq: -> using host @%p\n", host); + + /* Check if mapping already exists */ + virq = irq_find_mapping(host, hwirq); + if (virq != NO_IRQ) { + pr_debug("irq: -> existing mapping on virq %d\n", virq); + return virq; + } + + /* Allocate a virtual interrupt number */ + hint = hwirq % irq_virq_count; + virq = irq_alloc_virt(host, 1, hint); + if (virq == NO_IRQ) { + pr_debug("irq: -> virq allocation failed\n"); + return NO_IRQ; + } + + if (irq_setup_virq(host, virq, hwirq)) + return NO_IRQ; + + pr_debug("irq: irq %lu on host %s mapped to virtual irq %u\n", + hwirq, host->of_node ? host->of_node->full_name : "null", virq); + + return virq; +} +EXPORT_SYMBOL_GPL(irq_create_mapping); + +unsigned int irq_create_of_mapping(struct device_node *controller, + const u32 *intspec, unsigned int intsize) +{ + struct irq_host *host; + irq_hw_number_t hwirq; + unsigned int type = IRQ_TYPE_NONE; + unsigned int virq; + + if (controller == NULL) + host = irq_default_host; + else + host = irq_find_host(controller); + if (host == NULL) { + printk(KERN_WARNING "irq: no irq host found for %s !\n", + controller->full_name); + return NO_IRQ; + } + + /* If host has no translation, then we assume interrupt line */ + if (host->ops->xlate == NULL) + hwirq = intspec[0]; + else { + if (host->ops->xlate(host, controller, intspec, intsize, + &hwirq, &type)) + return NO_IRQ; + } + + /* Create mapping */ + virq = irq_create_mapping(host, hwirq); + if (virq == NO_IRQ) + return virq; + + /* Set type if specified and different than the current one */ + if (type != IRQ_TYPE_NONE && + type != (irqd_get_trigger_type(irq_get_irq_data(virq)))) + irq_set_irq_type(virq, type); + return virq; +} +EXPORT_SYMBOL_GPL(irq_create_of_mapping); + +void irq_dispose_mapping(unsigned int virq) +{ + struct irq_host *host; + irq_hw_number_t hwirq; + + if (virq == NO_IRQ) + return; + + /* Never unmap priority interrupts */ + if (virq < NR_PRIORITY_IRQS) + return; + + host = irq_map[virq].host; + if (WARN_ON(host == NULL)) + return; + + irq_set_status_flags(virq, IRQ_NOREQUEST); + + /* remove chip and handler */ + irq_set_chip_and_handler(virq, NULL, NULL); + + /* Make sure it's completed */ + synchronize_irq(virq); + + /* Tell the PIC about it */ + if (host->ops->unmap) + host->ops->unmap(host, virq); + smp_mb(); + + /* Clear reverse map */ + hwirq = irq_map[virq].hwirq; + switch (host->revmap_type) { + case IRQ_HOST_MAP_LINEAR: + if (hwirq < host->revmap_data.linear.size) + host->revmap_data.linear.revmap[hwirq] = NO_IRQ; + break; + case IRQ_HOST_MAP_TREE: + mutex_lock(&revmap_trees_mutex); + radix_tree_delete(&host->revmap_data.tree, hwirq); + mutex_unlock(&revmap_trees_mutex); + break; + } + + /* Destroy map */ + smp_mb(); + irq_map[virq].hwirq = host->inval_irq; + + irq_free_descs(virq, 1); + /* Free it */ + irq_free_virt(virq, 1); +} +EXPORT_SYMBOL_GPL(irq_dispose_mapping); + +unsigned int irq_find_mapping(struct irq_host *host, + irq_hw_number_t hwirq) +{ + unsigned int i; + unsigned int hint = hwirq % irq_virq_count; + + /* Look for default host if nececssary */ + if (host == NULL) + host = irq_default_host; + if (host == NULL) + return NO_IRQ; + + /* Slow path does a linear search of the map */ + i = hint; + do { + if (irq_map[i].host == host && + irq_map[i].hwirq == hwirq) + return i; + i++; + if (i >= irq_virq_count) + i = 4; + } while (i != hint); + return NO_IRQ; +} +EXPORT_SYMBOL_GPL(irq_find_mapping); + +unsigned int irq_radix_revmap_lookup(struct irq_host *host, + irq_hw_number_t hwirq) +{ + struct irq_map_entry *ptr; + unsigned int virq; + + if (WARN_ON_ONCE(host->revmap_type != IRQ_HOST_MAP_TREE)) + return irq_find_mapping(host, hwirq); + + /* + * The ptr returned references the static global irq_map. + * but freeing an irq can delete nodes along the path to + * do the lookup via call_rcu. + */ + rcu_read_lock(); + ptr = radix_tree_lookup(&host->revmap_data.tree, hwirq); + rcu_read_unlock(); + + /* + * If found in radix tree, then fine. + * Else fallback to linear lookup - this should not happen in practice + * as it means that we failed to insert the node in the radix tree. + */ + if (ptr) + virq = ptr - irq_map; + else + virq = irq_find_mapping(host, hwirq); + + return virq; +} + +void irq_radix_revmap_insert(struct irq_host *host, unsigned int virq, + irq_hw_number_t hwirq) +{ + if (WARN_ON(host->revmap_type != IRQ_HOST_MAP_TREE)) + return; + + if (virq != NO_IRQ) { + mutex_lock(&revmap_trees_mutex); + radix_tree_insert(&host->revmap_data.tree, hwirq, + &irq_map[virq]); + mutex_unlock(&revmap_trees_mutex); + } +} + +unsigned int irq_linear_revmap(struct irq_host *host, + irq_hw_number_t hwirq) +{ + unsigned int *revmap; + + if (WARN_ON_ONCE(host->revmap_type != IRQ_HOST_MAP_LINEAR)) + return irq_find_mapping(host, hwirq); + + /* Check revmap bounds */ + if (unlikely(hwirq >= host->revmap_data.linear.size)) + return irq_find_mapping(host, hwirq); + + /* Check if revmap was allocated */ + revmap = host->revmap_data.linear.revmap; + if (unlikely(revmap == NULL)) + return irq_find_mapping(host, hwirq); + + /* Fill up revmap with slow path if no mapping found */ + if (unlikely(revmap[hwirq] == NO_IRQ)) + revmap[hwirq] = irq_find_mapping(host, hwirq); + + return revmap[hwirq]; +} + +unsigned int irq_alloc_virt(struct irq_host *host, + unsigned int count, + unsigned int hint) +{ + unsigned long flags; + unsigned int i, j, found = NO_IRQ; + + if (count == 0 || count > (irq_virq_count - NR_PRIORITY_IRQS)) + return NO_IRQ; + + raw_spin_lock_irqsave(&irq_big_lock, flags); + + /* Use hint for 1 interrupt if any */ + if (count == 1 && hint >= NR_PRIORITY_IRQS && + hint < irq_virq_count && irq_map[hint].host == NULL) { + found = hint; + goto hint_found; + } + + /* Look for count consecutive numbers in the allocatable + * (non-legacy) space + */ + for (i = NR_PRIORITY_IRQS, j = 0; i < irq_virq_count; i++) { + if (irq_map[i].host != NULL) + j = 0; + else + j++; + + if (j == count) { + found = i - count + 1; + break; + } + } + if (found == NO_IRQ) { + raw_spin_unlock_irqrestore(&irq_big_lock, flags); + return NO_IRQ; + } + hint_found: + for (i = found; i < (found + count); i++) { + irq_map[i].hwirq = host->inval_irq; + smp_wmb(); + irq_map[i].host = host; + } + raw_spin_unlock_irqrestore(&irq_big_lock, flags); + return found; +} + +void irq_free_virt(unsigned int virq, unsigned int count) +{ + unsigned long flags; + unsigned int i; + + WARN_ON(virq < NR_PRIORITY_IRQS); + WARN_ON(count == 0 || (virq + count) > irq_virq_count); + + if (virq < NR_PRIORITY_IRQS) { + if (virq + count < NR_PRIORITY_IRQS) + return; + count -= NR_PRIORITY_IRQS - virq; + virq = NR_PRIORITY_IRQS; + } + + if (count > irq_virq_count || virq > irq_virq_count - count) { + if (virq > irq_virq_count) + return; + count = irq_virq_count - virq; + } + + raw_spin_lock_irqsave(&irq_big_lock, flags); + for (i = virq; i < (virq + count); i++) { + struct irq_host *host; + + host = irq_map[i].host; + irq_map[i].hwirq = host->inval_irq; + smp_wmb(); + irq_map[i].host = NULL; + } + raw_spin_unlock_irqrestore(&irq_big_lock, flags); +} + +#ifdef CONFIG_VIRQ_DEBUG +static int virq_debug_show(struct seq_file *m, void *private) +{ + unsigned long flags; + struct irq_desc *desc; + const char *p; + static const char none[] = "none"; + void *data; + int i; + + seq_printf(m, "%-5s %-7s %-15s %-18s %s\n", "virq", "hwirq", + "chip name", "chip data", "host name"); + + for (i = 1; i < nr_irqs; i++) { + desc = irq_to_desc(i); + if (!desc) + continue; + + raw_spin_lock_irqsave(&desc->lock, flags); + + if (desc->action && desc->action->handler) { + struct irq_chip *chip; + + seq_printf(m, "%5d ", i); + seq_printf(m, "0x%05lx ", irq_map[i].hwirq); + + chip = irq_desc_get_chip(desc); + if (chip && chip->name) + p = chip->name; + else + p = none; + seq_printf(m, "%-15s ", p); + + data = irq_desc_get_chip_data(desc); + seq_printf(m, "0x%16p ", data); + + if (irq_map[i].host && irq_map[i].host->of_node) + p = irq_map[i].host->of_node->full_name; + else + p = none; + seq_printf(m, "%s\n", p); + } + + raw_spin_unlock_irqrestore(&desc->lock, flags); + } + + return 0; +} + +static int virq_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, virq_debug_show, inode->i_private); +} + +static const struct file_operations virq_debug_fops = { + .open = virq_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init irq_debugfs_init(void) +{ + if (debugfs_create_file("virq_mapping", S_IRUGO, powerpc_debugfs_root, + NULL, &virq_debug_fops) == NULL) + return -ENOMEM; + + return 0; +} +device_initcall(irq_debugfs_init); +#endif /* CONFIG_VIRQ_DEBUG */ diff --git a/arch/c6x/kernel/module.c b/arch/c6x/kernel/module.c new file mode 100644 index 000000000000..5fc03f18f56c --- /dev/null +++ b/arch/c6x/kernel/module.c @@ -0,0 +1,123 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2005, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Thomas Charleux (thomas.charleux@jaluna.com) + * + * 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. + * + */ +#include <linux/moduleloader.h> +#include <linux/elf.h> +#include <linux/vmalloc.h> +#include <linux/kernel.h> + +static inline int fixup_pcr(u32 *ip, Elf32_Addr dest, u32 maskbits, int shift) +{ + u32 opcode; + long ep = (long)ip & ~31; + long delta = ((long)dest - ep) >> 2; + long mask = (1 << maskbits) - 1; + + if ((delta >> (maskbits - 1)) == 0 || + (delta >> (maskbits - 1)) == -1) { + opcode = *ip; + opcode &= ~(mask << shift); + opcode |= ((delta & mask) << shift); + *ip = opcode; + + pr_debug("REL PCR_S%d[%p] dest[%p] opcode[%08x]\n", + maskbits, ip, (void *)dest, opcode); + + return 0; + } + pr_err("PCR_S%d reloc %p -> %p out of range!\n", + maskbits, ip, (void *)dest); + + return -1; +} + +/* + * apply a RELA relocation + */ +int apply_relocate_add(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + Elf32_Rela *rel = (void *) sechdrs[relsec].sh_addr; + Elf_Sym *sym; + u32 *location, opcode; + unsigned int i; + Elf32_Addr v; + Elf_Addr offset = 0; + + pr_debug("Applying relocate section %u to %u with offset 0x%x\n", + relsec, sechdrs[relsec].sh_info, offset); + + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { + /* This is where to make the change */ + location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + + rel[i].r_offset - offset; + + /* This is the symbol it is referring to. Note that all + undefined symbols have been resolved. */ + sym = (Elf_Sym *)sechdrs[symindex].sh_addr + + ELF32_R_SYM(rel[i].r_info); + + /* this is the adjustment to be made */ + v = sym->st_value + rel[i].r_addend; + + switch (ELF32_R_TYPE(rel[i].r_info)) { + case R_C6000_ABS32: + pr_debug("RELA ABS32: [%p] = 0x%x\n", location, v); + *location = v; + break; + case R_C6000_ABS16: + pr_debug("RELA ABS16: [%p] = 0x%x\n", location, v); + *(u16 *)location = v; + break; + case R_C6000_ABS8: + pr_debug("RELA ABS8: [%p] = 0x%x\n", location, v); + *(u8 *)location = v; + break; + case R_C6000_ABS_L16: + opcode = *location; + opcode &= ~0x7fff80; + opcode |= ((v & 0xffff) << 7); + pr_debug("RELA ABS_L16[%p] v[0x%x] opcode[0x%x]\n", + location, v, opcode); + *location = opcode; + break; + case R_C6000_ABS_H16: + opcode = *location; + opcode &= ~0x7fff80; + opcode |= ((v >> 9) & 0x7fff80); + pr_debug("RELA ABS_H16[%p] v[0x%x] opcode[0x%x]\n", + location, v, opcode); + *location = opcode; + break; + case R_C6000_PCR_S21: + if (fixup_pcr(location, v, 21, 7)) + return -ENOEXEC; + break; + case R_C6000_PCR_S12: + if (fixup_pcr(location, v, 12, 16)) + return -ENOEXEC; + break; + case R_C6000_PCR_S10: + if (fixup_pcr(location, v, 10, 13)) + return -ENOEXEC; + break; + default: + pr_err("module %s: Unknown RELA relocation: %u\n", + me->name, ELF32_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + } + + return 0; +} diff --git a/arch/c6x/kernel/process.c b/arch/c6x/kernel/process.c new file mode 100644 index 000000000000..7ca8c41b03cd --- /dev/null +++ b/arch/c6x/kernel/process.c @@ -0,0 +1,265 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2004, 2006, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) + * + * 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. + * + */ +#include <linux/module.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/init_task.h> +#include <linux/tick.h> +#include <linux/mqueue.h> +#include <linux/syscalls.h> +#include <linux/reboot.h> + +#include <asm/syscalls.h> + +/* hooks for board specific support */ +void (*c6x_restart)(void); +void (*c6x_halt)(void); + +extern asmlinkage void ret_from_fork(void); + +static struct signal_struct init_signals = INIT_SIGNALS(init_signals); +static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand); + +/* + * Initial thread structure. + */ +union thread_union init_thread_union __init_task_data = { + INIT_THREAD_INFO(init_task) +}; + +/* + * Initial task structure. + */ +struct task_struct init_task = INIT_TASK(init_task); +EXPORT_SYMBOL(init_task); + +/* + * power off function, if any + */ +void (*pm_power_off)(void); +EXPORT_SYMBOL(pm_power_off); + +static void c6x_idle(void) +{ + unsigned long tmp; + + /* + * Put local_irq_enable and idle in same execute packet + * to make them atomic and avoid race to idle with + * interrupts enabled. + */ + asm volatile (" mvc .s2 CSR,%0\n" + " or .d2 1,%0,%0\n" + " mvc .s2 %0,CSR\n" + "|| idle\n" + : "=b"(tmp)); +} + +/* + * The idle loop for C64x + */ +void cpu_idle(void) +{ + /* endless idle loop with no priority at all */ + while (1) { + tick_nohz_idle_enter(); + rcu_idle_enter(); + while (1) { + local_irq_disable(); + if (need_resched()) { + local_irq_enable(); + break; + } + c6x_idle(); /* enables local irqs */ + } + rcu_idle_exit(); + tick_nohz_idle_exit(); + + preempt_enable_no_resched(); + schedule(); + preempt_disable(); + } +} + +static void halt_loop(void) +{ + printk(KERN_EMERG "System Halted, OK to turn off power\n"); + local_irq_disable(); + while (1) + asm volatile("idle\n"); +} + +void machine_restart(char *__unused) +{ + if (c6x_restart) + c6x_restart(); + halt_loop(); +} + +void machine_halt(void) +{ + if (c6x_halt) + c6x_halt(); + halt_loop(); +} + +void machine_power_off(void) +{ + if (pm_power_off) + pm_power_off(); + halt_loop(); +} + +static void kernel_thread_helper(int dummy, void *arg, int (*fn)(void *)) +{ + do_exit(fn(arg)); +} + +/* + * Create a kernel thread + */ +int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) +{ + struct pt_regs regs; + + /* + * copy_thread sets a4 to zero (child return from fork) + * so we can't just set things up to directly return to + * fn. + */ + memset(®s, 0, sizeof(regs)); + regs.b4 = (unsigned long) arg; + regs.a6 = (unsigned long) fn; + regs.pc = (unsigned long) kernel_thread_helper; + local_save_flags(regs.csr); + regs.csr |= 1; + regs.tsr = 5; /* Set GEE and GIE in TSR */ + + /* Ok, create the new process.. */ + return do_fork(flags | CLONE_VM | CLONE_UNTRACED, -1, ®s, + 0, NULL, NULL); +} +EXPORT_SYMBOL(kernel_thread); + +void flush_thread(void) +{ +} + +void exit_thread(void) +{ +} + +SYSCALL_DEFINE1(c6x_clone, struct pt_regs *, regs) +{ + unsigned long clone_flags; + unsigned long newsp; + + /* syscall puts clone_flags in A4 and usp in B4 */ + clone_flags = regs->orig_a4; + if (regs->b4) + newsp = regs->b4; + else + newsp = regs->sp; + + return do_fork(clone_flags, newsp, regs, 0, (int __user *)regs->a6, + (int __user *)regs->b6); +} + +/* + * Do necessary setup to start up a newly executed thread. + */ +void start_thread(struct pt_regs *regs, unsigned int pc, unsigned long usp) +{ + /* + * The binfmt loader will setup a "full" stack, but the C6X + * operates an "empty" stack. So we adjust the usp so that + * argc doesn't get destroyed if an interrupt is taken before + * it is read from the stack. + * + * NB: Library startup code needs to match this. + */ + usp -= 8; + + set_fs(USER_DS); + regs->pc = pc; + regs->sp = usp; + regs->tsr |= 0x40; /* set user mode */ + current->thread.usp = usp; +} + +/* + * Copy a new thread context in its stack. + */ +int copy_thread(unsigned long clone_flags, unsigned long usp, + unsigned long ustk_size, + struct task_struct *p, struct pt_regs *regs) +{ + struct pt_regs *childregs; + + childregs = task_pt_regs(p); + + *childregs = *regs; + childregs->a4 = 0; + + if (usp == -1) + /* case of __kernel_thread: we return to supervisor space */ + childregs->sp = (unsigned long)(childregs + 1); + else + /* Otherwise use the given stack */ + childregs->sp = usp; + + /* Set usp/ksp */ + p->thread.usp = childregs->sp; + /* switch_to uses stack to save/restore 14 callee-saved regs */ + thread_saved_ksp(p) = (unsigned long)childregs - 8; + p->thread.pc = (unsigned int) ret_from_fork; + p->thread.wchan = (unsigned long) ret_from_fork; +#ifdef __DSBT__ + { + unsigned long dp; + + asm volatile ("mv .S2 b14,%0\n" : "=b"(dp)); + + thread_saved_dp(p) = dp; + if (usp == -1) + childregs->dp = dp; + } +#endif + return 0; +} + +/* + * c6x_execve() executes a new program. + */ +SYSCALL_DEFINE4(c6x_execve, const char __user *, name, + const char __user *const __user *, argv, + const char __user *const __user *, envp, + struct pt_regs *, regs) +{ + int error; + char *filename; + + filename = getname(name); + error = PTR_ERR(filename); + if (IS_ERR(filename)) + goto out; + + error = do_execve(filename, argv, envp, regs); + putname(filename); +out: + return error; +} + +unsigned long get_wchan(struct task_struct *p) +{ + return p->thread.wchan; +} diff --git a/arch/c6x/kernel/ptrace.c b/arch/c6x/kernel/ptrace.c new file mode 100644 index 000000000000..3c494e84444d --- /dev/null +++ b/arch/c6x/kernel/ptrace.c @@ -0,0 +1,187 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2004, 2006, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) + * + * Updated for 2.6.34: Mark Salter <msalter@redhat.com> + * + * 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. + */ +#include <linux/ptrace.h> +#include <linux/tracehook.h> +#include <linux/regset.h> +#include <linux/elf.h> + +#include <asm/cacheflush.h> + +#define PT_REG_SIZE (sizeof(struct pt_regs)) + +/* + * Called by kernel/ptrace.c when detaching. + */ +void ptrace_disable(struct task_struct *child) +{ + /* nothing to do */ +} + +/* + * Get a register number from live pt_regs for the specified task. + */ +static inline long get_reg(struct task_struct *task, int regno) +{ + long *addr = (long *)task_pt_regs(task); + + if (regno == PT_TSR || regno == PT_CSR) + return 0; + + return addr[regno]; +} + +/* + * Write contents of register REGNO in task TASK. + */ +static inline int put_reg(struct task_struct *task, + int regno, + unsigned long data) +{ + unsigned long *addr = (unsigned long *)task_pt_regs(task); + + if (regno != PT_TSR && regno != PT_CSR) + addr[regno] = data; + + return 0; +} + +/* regset get/set implementations */ + +static int gpr_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user *ubuf) +{ + struct pt_regs *regs = task_pt_regs(target); + + return user_regset_copyout(&pos, &count, &kbuf, &ubuf, + regs, + 0, sizeof(*regs)); +} + +static int gpr_set(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + const void *kbuf, const void __user *ubuf) +{ + int ret; + struct pt_regs *regs = task_pt_regs(target); + + /* Don't copyin TSR or CSR */ + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + ®s, + 0, PT_TSR * sizeof(long)); + if (ret) + return ret; + + ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + PT_TSR * sizeof(long), + (PT_TSR + 1) * sizeof(long)); + if (ret) + return ret; + + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + ®s, + (PT_TSR + 1) * sizeof(long), + PT_CSR * sizeof(long)); + if (ret) + return ret; + + ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + PT_CSR * sizeof(long), + (PT_CSR + 1) * sizeof(long)); + if (ret) + return ret; + + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + ®s, + (PT_CSR + 1) * sizeof(long), -1); + return ret; +} + +enum c6x_regset { + REGSET_GPR, +}; + +static const struct user_regset c6x_regsets[] = { + [REGSET_GPR] = { + .core_note_type = NT_PRSTATUS, + .n = ELF_NGREG, + .size = sizeof(u32), + .align = sizeof(u32), + .get = gpr_get, + .set = gpr_set + }, +}; + +static const struct user_regset_view user_c6x_native_view = { + .name = "tic6x", + .e_machine = EM_TI_C6000, + .regsets = c6x_regsets, + .n = ARRAY_SIZE(c6x_regsets), +}; + +const struct user_regset_view *task_user_regset_view(struct task_struct *task) +{ + return &user_c6x_native_view; +} + +/* + * Perform ptrace request + */ +long arch_ptrace(struct task_struct *child, long request, + unsigned long addr, unsigned long data) +{ + int ret = 0; + + switch (request) { + /* + * write the word at location addr. + */ + case PTRACE_POKETEXT: + ret = generic_ptrace_pokedata(child, addr, data); + if (ret == 0 && request == PTRACE_POKETEXT) + flush_icache_range(addr, addr + 4); + break; + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + return ret; +} + +/* + * handle tracing of system call entry + * - return the revised system call number or ULONG_MAX to cause ENOSYS + */ +asmlinkage unsigned long syscall_trace_entry(struct pt_regs *regs) +{ + if (tracehook_report_syscall_entry(regs)) + /* tracing decided this syscall should not happen, so + * We'll return a bogus call number to get an ENOSYS + * error, but leave the original number in + * regs->orig_a4 + */ + return ULONG_MAX; + + return regs->b0; +} + +/* + * handle tracing of system call exit + */ +asmlinkage void syscall_trace_exit(struct pt_regs *regs) +{ + tracehook_report_syscall_exit(regs, 0); +} diff --git a/arch/c6x/kernel/setup.c b/arch/c6x/kernel/setup.c new file mode 100644 index 000000000000..0c07921747f4 --- /dev/null +++ b/arch/c6x/kernel/setup.c @@ -0,0 +1,510 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2004, 2006, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) + * + * 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. + */ +#include <linux/dma-mapping.h> +#include <linux/memblock.h> +#include <linux/seq_file.h> +#include <linux/bootmem.h> +#include <linux/clkdev.h> +#include <linux/initrd.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_fdt.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/cache.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/clk.h> +#include <linux/cpu.h> +#include <linux/fs.h> +#include <linux/of.h> + + +#include <asm/sections.h> +#include <asm/div64.h> +#include <asm/setup.h> +#include <asm/dscr.h> +#include <asm/clock.h> +#include <asm/soc.h> + +static const char *c6x_soc_name; + +int c6x_num_cores; +EXPORT_SYMBOL_GPL(c6x_num_cores); + +unsigned int c6x_silicon_rev; +EXPORT_SYMBOL_GPL(c6x_silicon_rev); + +/* + * Device status register. This holds information + * about device configuration needed by some drivers. + */ +unsigned int c6x_devstat; +EXPORT_SYMBOL_GPL(c6x_devstat); + +/* + * Some SoCs have fuse registers holding a unique MAC + * address. This is parsed out of the device tree with + * the resulting MAC being held here. + */ +unsigned char c6x_fuse_mac[6]; + +unsigned long memory_start; +unsigned long memory_end; + +unsigned long ram_start; +unsigned long ram_end; + +/* Uncached memory for DMA consistent use (memdma=) */ +static unsigned long dma_start __initdata; +static unsigned long dma_size __initdata; + +char c6x_command_line[COMMAND_LINE_SIZE]; + +#if defined(CONFIG_CMDLINE_BOOL) +static const char default_command_line[COMMAND_LINE_SIZE] __section(.cmdline) = + CONFIG_CMDLINE; +#endif + +struct cpuinfo_c6x { + const char *cpu_name; + const char *cpu_voltage; + const char *mmu; + const char *fpu; + char *cpu_rev; + unsigned int core_id; + char __cpu_rev[5]; +}; + +static DEFINE_PER_CPU(struct cpuinfo_c6x, cpu_data); + +unsigned int ticks_per_ns_scaled; +EXPORT_SYMBOL(ticks_per_ns_scaled); + +unsigned int c6x_core_freq; + +static void __init get_cpuinfo(void) +{ + unsigned cpu_id, rev_id, csr; + struct clk *coreclk = clk_get_sys(NULL, "core"); + unsigned long core_khz; + u64 tmp; + struct cpuinfo_c6x *p; + struct device_node *node, *np; + + p = &per_cpu(cpu_data, smp_processor_id()); + + if (!IS_ERR(coreclk)) + c6x_core_freq = clk_get_rate(coreclk); + else { + printk(KERN_WARNING + "Cannot find core clock frequency. Using 700MHz\n"); + c6x_core_freq = 700000000; + } + + core_khz = c6x_core_freq / 1000; + + tmp = (uint64_t)core_khz << C6X_NDELAY_SCALE; + do_div(tmp, 1000000); + ticks_per_ns_scaled = tmp; + + csr = get_creg(CSR); + cpu_id = csr >> 24; + rev_id = (csr >> 16) & 0xff; + + p->mmu = "none"; + p->fpu = "none"; + p->cpu_voltage = "unknown"; + + switch (cpu_id) { + case 0: + p->cpu_name = "C67x"; + p->fpu = "yes"; + break; + case 2: + p->cpu_name = "C62x"; + break; + case 8: + p->cpu_name = "C64x"; + break; + case 12: + p->cpu_name = "C64x"; + break; + case 16: + p->cpu_name = "C64x+"; + p->cpu_voltage = "1.2"; + break; + default: + p->cpu_name = "unknown"; + break; + } + + if (cpu_id < 16) { + switch (rev_id) { + case 0x1: + if (cpu_id > 8) { + p->cpu_rev = "DM640/DM641/DM642/DM643"; + p->cpu_voltage = "1.2 - 1.4"; + } else { + p->cpu_rev = "C6201"; + p->cpu_voltage = "2.5"; + } + break; + case 0x2: + p->cpu_rev = "C6201B/C6202/C6211"; + p->cpu_voltage = "1.8"; + break; + case 0x3: + p->cpu_rev = "C6202B/C6203/C6204/C6205"; + p->cpu_voltage = "1.5"; + break; + case 0x201: + p->cpu_rev = "C6701 revision 0 (early CPU)"; + p->cpu_voltage = "1.8"; + break; + case 0x202: + p->cpu_rev = "C6701/C6711/C6712"; + p->cpu_voltage = "1.8"; + break; + case 0x801: + p->cpu_rev = "C64x"; + p->cpu_voltage = "1.5"; + break; + default: + p->cpu_rev = "unknown"; + } + } else { + p->cpu_rev = p->__cpu_rev; + snprintf(p->__cpu_rev, sizeof(p->__cpu_rev), "0x%x", cpu_id); + } + + p->core_id = get_coreid(); + + node = of_find_node_by_name(NULL, "cpus"); + if (node) { + for_each_child_of_node(node, np) + if (!strcmp("cpu", np->name)) + ++c6x_num_cores; + of_node_put(node); + } + + node = of_find_node_by_name(NULL, "soc"); + if (node) { + if (of_property_read_string(node, "model", &c6x_soc_name)) + c6x_soc_name = "unknown"; + of_node_put(node); + } else + c6x_soc_name = "unknown"; + + printk(KERN_INFO "CPU%d: %s rev %s, %s volts, %uMHz\n", + p->core_id, p->cpu_name, p->cpu_rev, + p->cpu_voltage, c6x_core_freq / 1000000); +} + +/* + * Early parsing of the command line + */ +static u32 mem_size __initdata; + +/* "mem=" parsing. */ +static int __init early_mem(char *p) +{ + if (!p) + return -EINVAL; + + mem_size = memparse(p, &p); + /* don't remove all of memory when handling "mem={invalid}" */ + if (mem_size == 0) + return -EINVAL; + + return 0; +} +early_param("mem", early_mem); + +/* "memdma=<size>[@<address>]" parsing. */ +static int __init early_memdma(char *p) +{ + if (!p) + return -EINVAL; + + dma_size = memparse(p, &p); + if (*p == '@') + dma_start = memparse(p, &p); + + return 0; +} +early_param("memdma", early_memdma); + +int __init c6x_add_memory(phys_addr_t start, unsigned long size) +{ + static int ram_found __initdata; + + /* We only handle one bank (the one with PAGE_OFFSET) for now */ + if (ram_found) + return -EINVAL; + + if (start > PAGE_OFFSET || PAGE_OFFSET >= (start + size)) + return 0; + + ram_start = start; + ram_end = start + size; + + ram_found = 1; + return 0; +} + +/* + * Do early machine setup and device tree parsing. This is called very + * early on the boot process. + */ +notrace void __init machine_init(unsigned long dt_ptr) +{ + struct boot_param_header *dtb = __va(dt_ptr); + struct boot_param_header *fdt = (struct boot_param_header *)_fdt_start; + + /* interrupts must be masked */ + set_creg(IER, 2); + + /* + * Set the Interrupt Service Table (IST) to the beginning of the + * vector table. + */ + set_ist(_vectors_start); + + lockdep_init(); + + /* + * dtb is passed in from bootloader. + * fdt is linked in blob. + */ + if (dtb && dtb != fdt) + fdt = dtb; + + /* Do some early initialization based on the flat device tree */ + early_init_devtree(fdt); + + /* parse_early_param needs a boot_command_line */ + strlcpy(boot_command_line, c6x_command_line, COMMAND_LINE_SIZE); + parse_early_param(); +} + +void __init setup_arch(char **cmdline_p) +{ + int bootmap_size; + struct memblock_region *reg; + + printk(KERN_INFO "Initializing kernel\n"); + + /* Initialize command line */ + *cmdline_p = c6x_command_line; + + memory_end = ram_end; + memory_end &= ~(PAGE_SIZE - 1); + + if (mem_size && (PAGE_OFFSET + PAGE_ALIGN(mem_size)) < memory_end) + memory_end = PAGE_OFFSET + PAGE_ALIGN(mem_size); + + /* add block that this kernel can use */ + memblock_add(PAGE_OFFSET, memory_end - PAGE_OFFSET); + + /* reserve kernel text/data/bss */ + memblock_reserve(PAGE_OFFSET, + PAGE_ALIGN((unsigned long)&_end - PAGE_OFFSET)); + + if (dma_size) { + /* align to cacheability granularity */ + dma_size = CACHE_REGION_END(dma_size); + + if (!dma_start) + dma_start = memory_end - dma_size; + + /* align to cacheability granularity */ + dma_start = CACHE_REGION_START(dma_start); + + /* reserve DMA memory taken from kernel memory */ + if (memblock_is_region_memory(dma_start, dma_size)) + memblock_reserve(dma_start, dma_size); + } + + memory_start = PAGE_ALIGN((unsigned int) &_end); + + printk(KERN_INFO "Memory Start=%08lx, Memory End=%08lx\n", + memory_start, memory_end); + +#ifdef CONFIG_BLK_DEV_INITRD + /* + * Reserve initrd memory if in kernel memory. + */ + if (initrd_start < initrd_end) + if (memblock_is_region_memory(initrd_start, + initrd_end - initrd_start)) + memblock_reserve(initrd_start, + initrd_end - initrd_start); +#endif + + init_mm.start_code = (unsigned long) &_stext; + init_mm.end_code = (unsigned long) &_etext; + init_mm.end_data = memory_start; + init_mm.brk = memory_start; + + /* + * Give all the memory to the bootmap allocator, tell it to put the + * boot mem_map at the start of memory + */ + bootmap_size = init_bootmem_node(NODE_DATA(0), + memory_start >> PAGE_SHIFT, + PAGE_OFFSET >> PAGE_SHIFT, + memory_end >> PAGE_SHIFT); + memblock_reserve(memory_start, bootmap_size); + + unflatten_device_tree(); + + c6x_cache_init(); + + /* Set the whole external memory as non-cacheable */ + disable_caching(ram_start, ram_end - 1); + + /* Set caching of external RAM used by Linux */ + for_each_memblock(memory, reg) + enable_caching(CACHE_REGION_START(reg->base), + CACHE_REGION_START(reg->base + reg->size - 1)); + +#ifdef CONFIG_BLK_DEV_INITRD + /* + * Enable caching for initrd which falls outside kernel memory. + */ + if (initrd_start < initrd_end) { + if (!memblock_is_region_memory(initrd_start, + initrd_end - initrd_start)) + enable_caching(CACHE_REGION_START(initrd_start), + CACHE_REGION_START(initrd_end - 1)); + } +#endif + + /* + * Disable caching for dma coherent memory taken from kernel memory. + */ + if (dma_size && memblock_is_region_memory(dma_start, dma_size)) + disable_caching(dma_start, + CACHE_REGION_START(dma_start + dma_size - 1)); + + /* Initialize the coherent memory allocator */ + coherent_mem_init(dma_start, dma_size); + + /* + * Free all memory as a starting point. + */ + free_bootmem(PAGE_OFFSET, memory_end - PAGE_OFFSET); + + /* + * Then reserve memory which is already being used. + */ + for_each_memblock(reserved, reg) { + pr_debug("reserved - 0x%08x-0x%08x\n", + (u32) reg->base, (u32) reg->size); + reserve_bootmem(reg->base, reg->size, BOOTMEM_DEFAULT); + } + + max_low_pfn = PFN_DOWN(memory_end); + min_low_pfn = PFN_UP(memory_start); + max_mapnr = max_low_pfn - min_low_pfn; + + /* Get kmalloc into gear */ + paging_init(); + + /* + * Probe for Device State Configuration Registers. + * We have to do this early in case timer needs to be enabled + * through DSCR. + */ + dscr_probe(); + + /* We do this early for timer and core clock frequency */ + c64x_setup_clocks(); + + /* Get CPU info */ + get_cpuinfo(); + +#if defined(CONFIG_VT) && defined(CONFIG_DUMMY_CONSOLE) + conswitchp = &dummy_con; +#endif +} + +#define cpu_to_ptr(n) ((void *)((long)(n)+1)) +#define ptr_to_cpu(p) ((long)(p) - 1) + +static int show_cpuinfo(struct seq_file *m, void *v) +{ + int n = ptr_to_cpu(v); + struct cpuinfo_c6x *p = &per_cpu(cpu_data, n); + + if (n == 0) { + seq_printf(m, + "soc\t\t: %s\n" + "soc revision\t: 0x%x\n" + "soc cores\t: %d\n", + c6x_soc_name, c6x_silicon_rev, c6x_num_cores); + } + + seq_printf(m, + "\n" + "processor\t: %d\n" + "cpu\t\t: %s\n" + "core revision\t: %s\n" + "core voltage\t: %s\n" + "core id\t\t: %d\n" + "mmu\t\t: %s\n" + "fpu\t\t: %s\n" + "cpu MHz\t\t: %u\n" + "bogomips\t: %lu.%02lu\n\n", + n, + p->cpu_name, p->cpu_rev, p->cpu_voltage, + p->core_id, p->mmu, p->fpu, + (c6x_core_freq + 500000) / 1000000, + (loops_per_jiffy/(500000/HZ)), + (loops_per_jiffy/(5000/HZ))%100); + + return 0; +} + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < nr_cpu_ids ? cpu_to_ptr(*pos) : NULL; +} +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return NULL; +} +static void c_stop(struct seq_file *m, void *v) +{ +} + +const struct seq_operations cpuinfo_op = { + c_start, + c_stop, + c_next, + show_cpuinfo +}; + +static struct cpu cpu_devices[NR_CPUS]; + +static int __init topology_init(void) +{ + int i; + + for_each_present_cpu(i) + register_cpu(&cpu_devices[i], i); + + return 0; +} + +subsys_initcall(topology_init); diff --git a/arch/c6x/kernel/signal.c b/arch/c6x/kernel/signal.c new file mode 100644 index 000000000000..304f675826e9 --- /dev/null +++ b/arch/c6x/kernel/signal.c @@ -0,0 +1,377 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2004, 2006, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) + * + * Updated for 2.6.34: Mark Salter <msalter@redhat.com> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/uaccess.h> +#include <linux/syscalls.h> +#include <linux/tracehook.h> + +#include <asm/ucontext.h> +#include <asm/cacheflush.h> + + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +/* + * Do a signal return, undo the signal stack. + */ + +#define RETCODE_SIZE (9 << 2) /* 9 instructions = 36 bytes */ + +struct rt_sigframe { + struct siginfo __user *pinfo; + void __user *puc; + struct siginfo info; + struct ucontext uc; + unsigned long retcode[RETCODE_SIZE >> 2]; +}; + +static int restore_sigcontext(struct pt_regs *regs, + struct sigcontext __user *sc) +{ + int err = 0; + + /* The access_ok check was done by caller, so use __get_user here */ +#define COPY(x) (err |= __get_user(regs->x, &sc->sc_##x)) + + COPY(sp); COPY(a4); COPY(b4); COPY(a6); COPY(b6); COPY(a8); COPY(b8); + COPY(a0); COPY(a1); COPY(a2); COPY(a3); COPY(a5); COPY(a7); COPY(a9); + COPY(b0); COPY(b1); COPY(b2); COPY(b3); COPY(b5); COPY(b7); COPY(b9); + + COPY(a16); COPY(a17); COPY(a18); COPY(a19); + COPY(a20); COPY(a21); COPY(a22); COPY(a23); + COPY(a24); COPY(a25); COPY(a26); COPY(a27); + COPY(a28); COPY(a29); COPY(a30); COPY(a31); + COPY(b16); COPY(b17); COPY(b18); COPY(b19); + COPY(b20); COPY(b21); COPY(b22); COPY(b23); + COPY(b24); COPY(b25); COPY(b26); COPY(b27); + COPY(b28); COPY(b29); COPY(b30); COPY(b31); + + COPY(csr); COPY(pc); + +#undef COPY + + return err; +} + +asmlinkage int do_rt_sigreturn(struct pt_regs *regs) +{ + struct rt_sigframe __user *frame; + sigset_t set; + + /* + * Since we stacked the signal on a dword boundary, + * 'sp' should be dword aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (regs->sp & 7) + goto badframe; + + frame = (struct rt_sigframe __user *) ((unsigned long) regs->sp + 8); + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + goto badframe; + + return regs->a4; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +static int setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs, + unsigned long mask) +{ + int err = 0; + + err |= __put_user(mask, &sc->sc_mask); + + /* The access_ok check was done by caller, so use __put_user here */ +#define COPY(x) (err |= __put_user(regs->x, &sc->sc_##x)) + + COPY(sp); COPY(a4); COPY(b4); COPY(a6); COPY(b6); COPY(a8); COPY(b8); + COPY(a0); COPY(a1); COPY(a2); COPY(a3); COPY(a5); COPY(a7); COPY(a9); + COPY(b0); COPY(b1); COPY(b2); COPY(b3); COPY(b5); COPY(b7); COPY(b9); + + COPY(a16); COPY(a17); COPY(a18); COPY(a19); + COPY(a20); COPY(a21); COPY(a22); COPY(a23); + COPY(a24); COPY(a25); COPY(a26); COPY(a27); + COPY(a28); COPY(a29); COPY(a30); COPY(a31); + COPY(b16); COPY(b17); COPY(b18); COPY(b19); + COPY(b20); COPY(b21); COPY(b22); COPY(b23); + COPY(b24); COPY(b25); COPY(b26); COPY(b27); + COPY(b28); COPY(b29); COPY(b30); COPY(b31); + + COPY(csr); COPY(pc); + +#undef COPY + + return err; +} + +static inline void __user *get_sigframe(struct k_sigaction *ka, + struct pt_regs *regs, + unsigned long framesize) +{ + unsigned long sp = regs->sp; + + /* + * This is the X/Open sanctioned signal stack switching. + */ + if ((ka->sa.sa_flags & SA_ONSTACK) && sas_ss_flags(sp) == 0) + sp = current->sas_ss_sp + current->sas_ss_size; + + /* + * No matter what happens, 'sp' must be dword + * aligned. Otherwise, nasty things will happen + */ + return (void __user *)((sp - framesize) & ~7); +} + +static int setup_rt_frame(int signr, struct k_sigaction *ka, siginfo_t *info, + sigset_t *set, struct pt_regs *regs) +{ + struct rt_sigframe __user *frame; + unsigned long __user *retcode; + int err = 0; + + frame = get_sigframe(ka, regs, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto segv_and_exit; + + err |= __put_user(&frame->info, &frame->pinfo); + err |= __put_user(&frame->uc, &frame->puc); + err |= copy_siginfo_to_user(&frame->info, info); + + /* Clear all the bits of the ucontext we don't use. */ + err |= __clear_user(&frame->uc, offsetof(struct ucontext, uc_mcontext)); + + err |= setup_sigcontext(&frame->uc.uc_mcontext, regs, set->sig[0]); + err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + + /* Set up to return from userspace */ + retcode = (unsigned long __user *) &frame->retcode; + + /* The access_ok check was done above, so use __put_user here */ +#define COPY(x) (err |= __put_user(x, retcode++)) + + COPY(0x0000002AUL | (__NR_rt_sigreturn << 7)); + /* MVK __NR_rt_sigreturn,B0 */ + COPY(0x10000000UL); /* SWE */ + COPY(0x00006000UL); /* NOP 4 */ + COPY(0x00006000UL); /* NOP 4 */ + COPY(0x00006000UL); /* NOP 4 */ + COPY(0x00006000UL); /* NOP 4 */ + COPY(0x00006000UL); /* NOP 4 */ + COPY(0x00006000UL); /* NOP 4 */ + COPY(0x00006000UL); /* NOP 4 */ + +#undef COPY + + if (err) + goto segv_and_exit; + + flush_icache_range((unsigned long) &frame->retcode, + (unsigned long) &frame->retcode + RETCODE_SIZE); + + retcode = (unsigned long __user *) &frame->retcode; + + /* Change user context to branch to signal handler */ + regs->sp = (unsigned long) frame - 8; + regs->b3 = (unsigned long) retcode; + regs->pc = (unsigned long) ka->sa.sa_handler; + + /* Give the signal number to the handler */ + regs->a4 = signr; + + /* + * For realtime signals we must also set the second and third + * arguments for the signal handler. + * -- Peter Maydell <pmaydell@chiark.greenend.org.uk> 2000-12-06 + */ + regs->b4 = (unsigned long)&frame->info; + regs->a6 = (unsigned long)&frame->uc; + + return 0; + +segv_and_exit: + force_sigsegv(signr, current); + return -EFAULT; +} + +static inline void +handle_restart(struct pt_regs *regs, struct k_sigaction *ka, int has_handler) +{ + switch (regs->a4) { + case -ERESTARTNOHAND: + if (!has_handler) + goto do_restart; + regs->a4 = -EINTR; + break; + + case -ERESTARTSYS: + if (has_handler && !(ka->sa.sa_flags & SA_RESTART)) { + regs->a4 = -EINTR; + break; + } + /* fallthrough */ + case -ERESTARTNOINTR: +do_restart: + regs->a4 = regs->orig_a4; + regs->pc -= 4; + break; + } +} + +/* + * handle the actual delivery of a signal to userspace + */ +static int handle_signal(int sig, + siginfo_t *info, struct k_sigaction *ka, + sigset_t *oldset, struct pt_regs *regs, + int syscall) +{ + int ret; + + /* Are we from a system call? */ + if (syscall) { + /* If so, check system call restarting.. */ + switch (regs->a4) { + case -ERESTART_RESTARTBLOCK: + case -ERESTARTNOHAND: + regs->a4 = -EINTR; + break; + + case -ERESTARTSYS: + if (!(ka->sa.sa_flags & SA_RESTART)) { + regs->a4 = -EINTR; + break; + } + + /* fallthrough */ + case -ERESTARTNOINTR: + regs->a4 = regs->orig_a4; + regs->pc -= 4; + } + } + + /* Set up the stack frame */ + ret = setup_rt_frame(sig, ka, info, oldset, regs); + if (ret == 0) { + spin_lock_irq(¤t->sighand->siglock); + sigorsets(¤t->blocked, ¤t->blocked, + &ka->sa.sa_mask); + if (!(ka->sa.sa_flags & SA_NODEFER)) + sigaddset(¤t->blocked, sig); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + } + + return ret; +} + +/* + * handle a potential signal + */ +static void do_signal(struct pt_regs *regs, int syscall) +{ + struct k_sigaction ka; + siginfo_t info; + sigset_t *oldset; + int signr; + + /* we want the common case to go fast, which is why we may in certain + * cases get here from kernel mode */ + if (!user_mode(regs)) + return; + + if (test_thread_flag(TIF_RESTORE_SIGMASK)) + oldset = ¤t->saved_sigmask; + else + oldset = ¤t->blocked; + + signr = get_signal_to_deliver(&info, &ka, regs, NULL); + if (signr > 0) { + if (handle_signal(signr, &info, &ka, oldset, + regs, syscall) == 0) { + /* a signal was successfully delivered; the saved + * sigmask will have been stored in the signal frame, + * and will be restored by sigreturn, so we can simply + * clear the TIF_RESTORE_SIGMASK flag */ + if (test_thread_flag(TIF_RESTORE_SIGMASK)) + clear_thread_flag(TIF_RESTORE_SIGMASK); + + tracehook_signal_handler(signr, &info, &ka, regs, 0); + } + + return; + } + + /* did we come from a system call? */ + if (syscall) { + /* restart the system call - no handlers present */ + switch (regs->a4) { + case -ERESTARTNOHAND: + case -ERESTARTSYS: + case -ERESTARTNOINTR: + regs->a4 = regs->orig_a4; + regs->pc -= 4; + break; + + case -ERESTART_RESTARTBLOCK: + regs->a4 = regs->orig_a4; + regs->b0 = __NR_restart_syscall; + regs->pc -= 4; + break; + } + } + + /* if there's no signal to deliver, we just put the saved sigmask + * back */ + if (test_thread_flag(TIF_RESTORE_SIGMASK)) { + clear_thread_flag(TIF_RESTORE_SIGMASK); + sigprocmask(SIG_SETMASK, ¤t->saved_sigmask, NULL); + } +} + +/* + * notification of userspace execution resumption + * - triggered by current->work.notify_resume + */ +asmlinkage void do_notify_resume(struct pt_regs *regs, u32 thread_info_flags, + int syscall) +{ + /* deal with pending signal delivery */ + if (thread_info_flags & ((1 << TIF_SIGPENDING) | + (1 << TIF_RESTORE_SIGMASK))) + do_signal(regs, syscall); + + if (thread_info_flags & (1 << TIF_NOTIFY_RESUME)) { + clear_thread_flag(TIF_NOTIFY_RESUME); + tracehook_notify_resume(regs); + if (current->replacement_session_keyring) + key_replace_session_keyring(); + } +} diff --git a/arch/c6x/kernel/soc.c b/arch/c6x/kernel/soc.c new file mode 100644 index 000000000000..dd45bc39af0e --- /dev/null +++ b/arch/c6x/kernel/soc.c @@ -0,0 +1,91 @@ +/* + * Miscellaneous SoC-specific hooks. + * + * Copyright (C) 2011 Texas Instruments Incorporated + * Author: Mark Salter <msalter@redhat.com> + * + * 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. + */ +#include <linux/module.h> +#include <linux/ctype.h> +#include <linux/etherdevice.h> +#include <asm/system.h> +#include <asm/setup.h> +#include <asm/soc.h> + +struct soc_ops soc_ops; + +int soc_get_exception(void) +{ + if (!soc_ops.get_exception) + return -1; + return soc_ops.get_exception(); +} + +void soc_assert_event(unsigned int evt) +{ + if (soc_ops.assert_event) + soc_ops.assert_event(evt); +} + +static u8 cmdline_mac[6]; + +static int __init get_mac_addr_from_cmdline(char *str) +{ + int count, i, val; + + for (count = 0; count < 6 && *str; count++, str += 3) { + if (!isxdigit(str[0]) || !isxdigit(str[1])) + return 0; + if (str[2] != ((count < 5) ? ':' : '\0')) + return 0; + + for (i = 0, val = 0; i < 2; i++) { + val = val << 4; + val |= isdigit(str[i]) ? + str[i] - '0' : toupper(str[i]) - 'A' + 10; + } + cmdline_mac[count] = val; + } + return 1; +} +__setup("emac_addr=", get_mac_addr_from_cmdline); + +/* + * Setup the MAC address for SoC ethernet devices. + * + * Before calling this function, the ethernet driver will have + * initialized the addr with local-mac-address from the device + * tree (if found). Allow command line to override, but not + * the fused address. + */ +int soc_mac_addr(unsigned int index, u8 *addr) +{ + int i, have_dt_mac = 0, have_cmdline_mac = 0, have_fuse_mac = 0; + + for (i = 0; i < 6; i++) { + if (cmdline_mac[i]) + have_cmdline_mac = 1; + if (c6x_fuse_mac[i]) + have_fuse_mac = 1; + if (addr[i]) + have_dt_mac = 1; + } + + /* cmdline overrides all */ + if (have_cmdline_mac) + memcpy(addr, cmdline_mac, 6); + else if (!have_dt_mac) { + if (have_fuse_mac) + memcpy(addr, c6x_fuse_mac, 6); + else + random_ether_addr(addr); + } + + /* adjust for specific EMAC device */ + addr[5] += index * c6x_num_cores; + return 1; +} +EXPORT_SYMBOL_GPL(soc_mac_addr); diff --git a/arch/c6x/kernel/switch_to.S b/arch/c6x/kernel/switch_to.S new file mode 100644 index 000000000000..09177ed0fa5c --- /dev/null +++ b/arch/c6x/kernel/switch_to.S @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 Texas Instruments Incorporated + * Author: Mark Salter (msalter@redhat.com) + * + * 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. + */ + +#include <linux/linkage.h> +#include <asm/asm-offsets.h> + +#define SP B15 + + /* + * void __switch_to(struct thread_info *prev, + * struct thread_info *next, + * struct task_struct *tsk) ; + */ +ENTRY(__switch_to) + LDDW .D2T2 *+B4(THREAD_B15_14),B7:B6 + || MV .L2X A4,B5 ; prev + || MV .L1X B4,A5 ; next + || MVC .S2 RILC,B1 + + STW .D2T2 B3,*+B5(THREAD_PC) + || STDW .D1T1 A13:A12,*+A4(THREAD_A13_12) + || MVC .S2 ILC,B0 + + LDW .D2T2 *+B4(THREAD_PC),B3 + || LDDW .D1T1 *+A5(THREAD_A13_12),A13:A12 + + STDW .D1T1 A11:A10,*+A4(THREAD_A11_10) + || STDW .D2T2 B1:B0,*+B5(THREAD_RICL_ICL) +#ifndef __DSBT__ + || MVKL .S2 current_ksp,B1 +#endif + + STDW .D2T2 B15:B14,*+B5(THREAD_B15_14) + || STDW .D1T1 A15:A14,*+A4(THREAD_A15_14) +#ifndef __DSBT__ + || MVKH .S2 current_ksp,B1 +#endif + + ;; Switch to next SP + MV .S2 B7,SP +#ifdef __DSBT__ + || STW .D2T2 B7,*+B14(current_ksp) +#else + || STW .D2T2 B7,*B1 + || MV .L2 B6,B14 +#endif + || LDDW .D1T1 *+A5(THREAD_RICL_ICL),A1:A0 + + STDW .D2T2 B11:B10,*+B5(THREAD_B11_10) + || LDDW .D1T1 *+A5(THREAD_A15_14),A15:A14 + + STDW .D2T2 B13:B12,*+B5(THREAD_B13_12) + || LDDW .D1T1 *+A5(THREAD_A11_10),A11:A10 + + B .S2 B3 ; return in next E1 + || LDDW .D2T2 *+B4(THREAD_B13_12),B13:B12 + + LDDW .D2T2 *+B4(THREAD_B11_10),B11:B10 + NOP + + MV .L2X A0,B0 + || MV .S1 A6,A4 + + MVC .S2 B0,ILC + || MV .L2X A1,B1 + + MVC .S2 B1,RILC +ENDPROC(__switch_to) diff --git a/arch/c6x/kernel/sys_c6x.c b/arch/c6x/kernel/sys_c6x.c new file mode 100644 index 000000000000..3e9bdfbee8ad --- /dev/null +++ b/arch/c6x/kernel/sys_c6x.c @@ -0,0 +1,74 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2004, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) + * + * 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. + */ +#include <linux/module.h> +#include <linux/syscalls.h> +#include <linux/uaccess.h> + +#include <asm/syscalls.h> + +#ifdef CONFIG_ACCESS_CHECK +int _access_ok(unsigned long addr, unsigned long size) +{ + if (!size) + return 1; + + if (!addr || addr > (0xffffffffUL - (size - 1))) + goto _bad_access; + + if (segment_eq(get_fs(), KERNEL_DS)) + return 1; + + if (memory_start <= addr && (addr + size - 1) < memory_end) + return 1; + +_bad_access: + pr_debug("Bad access attempt: pid[%d] addr[%08lx] size[0x%lx]\n", + current->pid, addr, size); + return 0; +} +EXPORT_SYMBOL(_access_ok); +#endif + +/* sys_cache_sync -- sync caches over given range */ +asmlinkage int sys_cache_sync(unsigned long s, unsigned long e) +{ + L1D_cache_block_writeback_invalidate(s, e); + L1P_cache_block_invalidate(s, e); + + return 0; +} + +/* Provide the actual syscall number to call mapping. */ +#undef __SYSCALL +#define __SYSCALL(nr, call) [nr] = (call), + +/* + * Use trampolines + */ +#define sys_pread64 sys_pread_c6x +#define sys_pwrite64 sys_pwrite_c6x +#define sys_truncate64 sys_truncate64_c6x +#define sys_ftruncate64 sys_ftruncate64_c6x +#define sys_fadvise64 sys_fadvise64_c6x +#define sys_fadvise64_64 sys_fadvise64_64_c6x +#define sys_fallocate sys_fallocate_c6x + +/* Use sys_mmap_pgoff directly */ +#define sys_mmap2 sys_mmap_pgoff + +/* + * Note that we can't include <linux/unistd.h> here since the header + * guard will defeat us; <asm/unistd.h> checks for __SYSCALL as well. + */ +void *sys_call_table[__NR_syscalls] = { + [0 ... __NR_syscalls-1] = sys_ni_syscall, +#include <asm/unistd.h> +}; diff --git a/arch/c6x/kernel/time.c b/arch/c6x/kernel/time.c new file mode 100644 index 000000000000..4c9f136165f7 --- /dev/null +++ b/arch/c6x/kernel/time.c @@ -0,0 +1,65 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2004, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/clocksource.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/timex.h> +#include <linux/profile.h> + +#include <asm/timer64.h> + +static u32 sched_clock_multiplier; +#define SCHED_CLOCK_SHIFT 16 + +static cycle_t tsc_read(struct clocksource *cs) +{ + return get_cycles(); +} + +static struct clocksource clocksource_tsc = { + .name = "timestamp", + .rating = 300, + .read = tsc_read, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +/* + * scheduler clock - returns current time in nanoseconds. + */ +u64 sched_clock(void) +{ + u64 tsc = get_cycles(); + + return (tsc * sched_clock_multiplier) >> SCHED_CLOCK_SHIFT; +} + +void time_init(void) +{ + u64 tmp = (u64)NSEC_PER_SEC << SCHED_CLOCK_SHIFT; + + do_div(tmp, c6x_core_freq); + sched_clock_multiplier = tmp; + + clocksource_register_hz(&clocksource_tsc, c6x_core_freq); + + /* write anything into TSCL to enable counting */ + set_creg(TSCL, 0); + + /* probe for timer64 event timer */ + timer64_init(); +} diff --git a/arch/c6x/kernel/traps.c b/arch/c6x/kernel/traps.c new file mode 100644 index 000000000000..f50e3edd6dad --- /dev/null +++ b/arch/c6x/kernel/traps.c @@ -0,0 +1,423 @@ +/* + * Port on Texas Instruments TMS320C6x architecture + * + * Copyright (C) 2004, 2006, 2009, 2010, 2011 Texas Instruments Incorporated + * Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) + * + * 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. + */ +#include <linux/module.h> +#include <linux/ptrace.h> +#include <linux/kallsyms.h> +#include <linux/bug.h> + +#include <asm/soc.h> +#include <asm/traps.h> + +int (*c6x_nmi_handler)(struct pt_regs *regs); + +void __init trap_init(void) +{ + ack_exception(EXCEPT_TYPE_NXF); + ack_exception(EXCEPT_TYPE_EXC); + ack_exception(EXCEPT_TYPE_IXF); + ack_exception(EXCEPT_TYPE_SXF); + enable_exception(); +} + +void show_regs(struct pt_regs *regs) +{ + pr_err("\n"); + pr_err("PC: %08lx SP: %08lx\n", regs->pc, regs->sp); + pr_err("Status: %08lx ORIG_A4: %08lx\n", regs->csr, regs->orig_a4); + pr_err("A0: %08lx B0: %08lx\n", regs->a0, regs->b0); + pr_err("A1: %08lx B1: %08lx\n", regs->a1, regs->b1); + pr_err("A2: %08lx B2: %08lx\n", regs->a2, regs->b2); + pr_err("A3: %08lx B3: %08lx\n", regs->a3, regs->b3); + pr_err("A4: %08lx B4: %08lx\n", regs->a4, regs->b4); + pr_err("A5: %08lx B5: %08lx\n", regs->a5, regs->b5); + pr_err("A6: %08lx B6: %08lx\n", regs->a6, regs->b6); + pr_err("A7: %08lx B7: %08lx\n", regs->a7, regs->b7); + pr_err("A8: %08lx B8: %08lx\n", regs->a8, regs->b8); + pr_err("A9: %08lx B9: %08lx\n", regs->a9, regs->b9); + pr_err("A10: %08lx B10: %08lx\n", regs->a10, regs->b10); + pr_err("A11: %08lx B11: %08lx\n", regs->a11, regs->b11); + pr_err("A12: %08lx B12: %08lx\n", regs->a12, regs->b12); + pr_err("A13: %08lx B13: %08lx\n", regs->a13, regs->b13); + pr_err("A14: %08lx B14: %08lx\n", regs->a14, regs->dp); + pr_err("A15: %08lx B15: %08lx\n", regs->a15, regs->sp); + pr_err("A16: %08lx B16: %08lx\n", regs->a16, regs->b16); + pr_err("A17: %08lx B17: %08lx\n", regs->a17, regs->b17); + pr_err("A18: %08lx B18: %08lx\n", regs->a18, regs->b18); + pr_err("A19: %08lx B19: %08lx\n", regs->a19, regs->b19); + pr_err("A20: %08lx B20: %08lx\n", regs->a20, regs->b20); + pr_err("A21: %08lx B21: %08lx\n", regs->a21, regs->b21); + pr_err("A22: %08lx B22: %08lx\n", regs->a22, regs->b22); + pr_err("A23: %08lx B23: %08lx\n", regs->a23, regs->b23); + pr_err("A24: %08lx B24: %08lx\n", regs->a24, regs->b24); + pr_err("A25: %08lx B25: %08lx\n", regs->a25, regs->b25); + pr_err("A26: %08lx B26: %08lx\n", regs->a26, regs->b26); + pr_err("A27: %08lx B27: %08lx\n", regs->a27, regs->b27); + pr_err("A28: %08lx B28: %08lx\n", regs->a28, regs->b28); + pr_err("A29: %08lx B29: %08lx\n", regs->a29, regs->b29); + pr_err("A30: %08lx B30: %08lx\n", regs->a30, regs->b30); + pr_err("A31: %08lx B31: %08lx\n", regs->a31, regs->b31); +} + +void dump_stack(void) +{ + unsigned long stack; + + show_stack(current, &stack); +} +EXPORT_SYMBOL(dump_stack); + + +void die(char *str, struct pt_regs *fp, int nr) +{ + console_verbose(); + pr_err("%s: %08x\n", str, nr); + show_regs(fp); + + pr_err("Process %s (pid: %d, stackpage=%08lx)\n", + current->comm, current->pid, (PAGE_SIZE + + (unsigned long) current)); + + dump_stack(); + while (1) + ; +} + +static void die_if_kernel(char *str, struct pt_regs *fp, int nr) +{ + if (user_mode(fp)) + return; + + die(str, fp, nr); +} + + +/* Internal exceptions */ +static struct exception_info iexcept_table[10] = { + { "Oops - instruction fetch", SIGBUS, BUS_ADRERR }, + { "Oops - fetch packet", SIGBUS, BUS_ADRERR }, + { "Oops - execute packet", SIGILL, ILL_ILLOPC }, + { "Oops - undefined instruction", SIGILL, ILL_ILLOPC }, + { "Oops - resource conflict", SIGILL, ILL_ILLOPC }, + { "Oops - resource access", SIGILL, ILL_PRVREG }, + { "Oops - privilege", SIGILL, ILL_PRVOPC }, + { "Oops - loops buffer", SIGILL, ILL_ILLOPC }, + { "Oops - software exception", SIGILL, ILL_ILLTRP }, + { "Oops - unknown exception", SIGILL, ILL_ILLOPC } +}; + +/* External exceptions */ +static struct exception_info eexcept_table[128] = { + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - external exception", SIGBUS, BUS_ADRERR }, + { "Oops - CPU memory protection fault", SIGSEGV, SEGV_ACCERR }, + { "Oops - CPU memory protection fault in L1P", SIGSEGV, SEGV_ACCERR }, + { "Oops - DMA memory protection fault in L1P", SIGSEGV, SEGV_ACCERR }, + { "Oops - CPU memory protection fault in L1D", SIGSEGV, SEGV_ACCERR }, + { "Oops - DMA memory protection fault in L1D", SIGSEGV, SEGV_ACCERR }, + { "Oops - CPU memory protection fault in L2", SIGSEGV, SEGV_ACCERR }, + { "Oops - DMA memory protection fault in L2", SIGSEGV, SEGV_ACCERR }, + { "Oops - EMC CPU memory protection fault", SIGSEGV, SEGV_ACCERR }, + { "Oops - EMC bus error", SIGBUS, BUS_ADRERR } +}; + +static void do_trap(struct exception_info *except_info, struct pt_regs *regs) +{ + unsigned long addr = instruction_pointer(regs); + siginfo_t info; + + if (except_info->code != TRAP_BRKPT) + pr_err("TRAP: %s PC[0x%lx] signo[%d] code[%d]\n", + except_info->kernel_str, regs->pc, + except_info->signo, except_info->code); + + die_if_kernel(except_info->kernel_str, regs, addr); + + info.si_signo = except_info->signo; + info.si_errno = 0; + info.si_code = except_info->code; + info.si_addr = (void __user *)addr; + + force_sig_info(except_info->signo, &info, current); +} + +/* + * Process an internal exception (non maskable) + */ +static int process_iexcept(struct pt_regs *regs) +{ + unsigned int iexcept_report = get_iexcept(); + unsigned int iexcept_num; + + ack_exception(EXCEPT_TYPE_IXF); + + pr_err("IEXCEPT: PC[0x%lx]\n", regs->pc); + + while (iexcept_report) { + iexcept_num = __ffs(iexcept_report); + iexcept_report &= ~(1 << iexcept_num); + set_iexcept(iexcept_report); + if (*(unsigned int *)regs->pc == BKPT_OPCODE) { + /* This is a breakpoint */ + struct exception_info bkpt_exception = { + "Oops - undefined instruction", + SIGTRAP, TRAP_BRKPT + }; + do_trap(&bkpt_exception, regs); + iexcept_report &= ~(0xFF); + set_iexcept(iexcept_report); + continue; + } + + do_trap(&iexcept_table[iexcept_num], regs); + } + return 0; +} + +/* + * Process an external exception (maskable) + */ +static void process_eexcept(struct pt_regs *regs) +{ + int evt; + + pr_err("EEXCEPT: PC[0x%lx]\n", regs->pc); + + while ((evt = soc_get_exception()) >= 0) + do_trap(&eexcept_table[evt], regs); + + ack_exception(EXCEPT_TYPE_EXC); +} + +/* + * Main exception processing + */ +asmlinkage int process_exception(struct pt_regs *regs) +{ + unsigned int type; + unsigned int type_num; + unsigned int ie_num = 9; /* default is unknown exception */ + + while ((type = get_except_type()) != 0) { + type_num = fls(type) - 1; + + switch (type_num) { + case EXCEPT_TYPE_NXF: + ack_exception(EXCEPT_TYPE_NXF); + if (c6x_nmi_handler) + (c6x_nmi_handler)(regs); + else + pr_alert("NMI interrupt!\n"); + break; + + case EXCEPT_TYPE_IXF: + if (process_iexcept(regs)) + return 1; + break; + + case EXCEPT_TYPE_EXC: + process_eexcept(regs); + break; + + case EXCEPT_TYPE_SXF: + ie_num = 8; + default: + ack_exception(type_num); + do_trap(&iexcept_table[ie_num], regs); + break; + } + } + return 0; +} + +static int kstack_depth_to_print = 48; + +static void show_trace(unsigned long *stack, unsigned long *endstack) +{ + unsigned long addr; + int i; + + pr_debug("Call trace:"); + i = 0; + while (stack + 1 <= endstack) { + addr = *stack++; + /* + * If the address is either in the text segment of the + * kernel, or in the region which contains vmalloc'ed + * memory, it *may* be the address of a calling + * routine; if so, print it so that someone tracing + * down the cause of the crash will be able to figure + * out the call path that was taken. + */ + if (__kernel_text_address(addr)) { +#ifndef CONFIG_KALLSYMS + if (i % 5 == 0) + pr_debug("\n "); +#endif + pr_debug(" [<%08lx>]", addr); + print_symbol(" %s\n", addr); + i++; + } + } + pr_debug("\n"); +} + +void show_stack(struct task_struct *task, unsigned long *stack) +{ + unsigned long *p, *endstack; + int i; + + if (!stack) { + if (task && task != current) + /* We know this is a kernel stack, + so this is the start/end */ + stack = (unsigned long *)thread_saved_ksp(task); + else + stack = (unsigned long *)&stack; + } + endstack = (unsigned long *)(((unsigned long)stack + THREAD_SIZE - 1) + & -THREAD_SIZE); + + pr_debug("Stack from %08lx:", (unsigned long)stack); + for (i = 0, p = stack; i < kstack_depth_to_print; i++) { + if (p + 1 > endstack) + break; + if (i % 8 == 0) + pr_cont("\n "); + pr_cont(" %08lx", *p++); + } + pr_cont("\n"); + show_trace(stack, endstack); +} + +int is_valid_bugaddr(unsigned long addr) +{ + return __kernel_text_address(addr); +} diff --git a/arch/c6x/kernel/vectors.S b/arch/c6x/kernel/vectors.S new file mode 100644 index 000000000000..c95c66fc71e8 --- /dev/null +++ b/arch/c6x/kernel/vectors.S @@ -0,0 +1,81 @@ +; +; Port on Texas Instruments TMS320C6x architecture +; +; Copyright (C) 2004, 2006, 2009, 2010, 2011 Texas Instruments Incorporated +; Author: Aurelien Jacquiot (aurelien.jacquiot@jaluna.com) +; +; 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 section handles all the interrupt vector routines. +; At RESET the processor sets up the DRAM timing parameters and +; branches to the label _c_int00 which handles initialization for the C code. +; + +#define ALIGNMENT 5 + + .macro IRQVEC name, handler + .align ALIGNMENT + .hidden \name + .global \name +\name: +#ifdef CONFIG_C6X_BIG_KERNEL + STW .D2T1 A0,*B15--[2] + || MVKL .S1 \handler,A0 + MVKH .S1 \handler,A0 + B .S2X A0 + LDW .D2T1 *++B15[2],A0 + NOP 4 + NOP + NOP + .endm +#else /* CONFIG_C6X_BIG_KERNEL */ + B .S2 \handler + NOP + NOP + NOP + NOP + NOP + NOP + NOP + .endm +#endif /* CONFIG_C6X_BIG_KERNEL */ + + .sect ".vectors","ax" + .align ALIGNMENT + .global RESET + .hidden RESET +RESET: +#ifdef CONFIG_C6X_BIG_KERNEL + MVKL .S1 _c_int00,A0 ; branch to _c_int00 + MVKH .S1 _c_int00,A0 + B .S2X A0 +#else + B .S2 _c_int00 + NOP + NOP +#endif + NOP + NOP + NOP + NOP + NOP + + + IRQVEC NMI,_nmi_handler ; NMI interrupt + IRQVEC AINT,_bad_interrupt ; reserved + IRQVEC MSGINT,_bad_interrupt ; reserved + + IRQVEC INT4,_int4_handler + IRQVEC INT5,_int5_handler + IRQVEC INT6,_int6_handler + IRQVEC INT7,_int7_handler + IRQVEC INT8,_int8_handler + IRQVEC INT9,_int9_handler + IRQVEC INT10,_int10_handler + IRQVEC INT11,_int11_handler + IRQVEC INT12,_int12_handler + IRQVEC INT13,_int13_handler + IRQVEC INT14,_int14_handler + IRQVEC INT15,_int15_handler diff --git a/arch/c6x/kernel/vmlinux.lds.S b/arch/c6x/kernel/vmlinux.lds.S new file mode 100644 index 000000000000..1d81c4c129ec --- /dev/null +++ b/arch/c6x/kernel/vmlinux.lds.S @@ -0,0 +1,162 @@ +/* + * ld script for the c6x kernel + * + * Copyright (C) 2010, 2011 Texas Instruments Incorporated + * Mark Salter <msalter@redhat.com> + */ +#include <asm-generic/vmlinux.lds.h> +#include <asm/thread_info.h> +#include <asm/page.h> + +ENTRY(_c_int00) + +#if defined(CONFIG_CPU_BIG_ENDIAN) +jiffies = jiffies_64 + 4; +#else +jiffies = jiffies_64; +#endif + +#define READONLY_SEGMENT_START \ + . = PAGE_OFFSET; +#define READWRITE_SEGMENT_START \ + . = ALIGN(128); \ + _data_lma = .; + +SECTIONS +{ + /* + * Start kernel read only segment + */ + READONLY_SEGMENT_START + + .vectors : + { + _vectors_start = .; + *(.vectors) + . = ALIGN(0x400); + _vectors_end = .; + } + + . = ALIGN(0x1000); + .cmdline : + { + *(.cmdline) + } + + /* + * This section contains data which may be shared with other + * cores. It needs to be a fixed offset from PAGE_OFFSET + * regardless of kernel configuration. + */ + .virtio_ipc_dev : + { + *(.virtio_ipc_dev) + } + + . = ALIGN(PAGE_SIZE); + .init : + { + _stext = .; + _sinittext = .; + HEAD_TEXT + INIT_TEXT + _einittext = .; + } + + __init_begin = _stext; + INIT_DATA_SECTION(16) + + PERCPU_SECTION(128) + + . = ALIGN(PAGE_SIZE); + __init_end = .; + + .text : + { + _text = .; + TEXT_TEXT + SCHED_TEXT + LOCK_TEXT + IRQENTRY_TEXT + KPROBES_TEXT + *(.fixup) + *(.gnu.warning) + } + + EXCEPTION_TABLE(16) + NOTES + + RO_DATA_SECTION(PAGE_SIZE) + .const : + { + *(.const .const.* .gnu.linkonce.r.*) + *(.switch) + } + + . = ALIGN (8) ; + __fdt_blob : AT(ADDR(__fdt_blob) - LOAD_OFFSET) + { + _fdt_start = . ; /* place for fdt blob */ + *(__fdt_blob) ; /* Any link-placed DTB */ + BYTE(0); /* section always has contents */ + . = _fdt_start + 0x4000; /* Pad up to 16kbyte */ + _fdt_end = . ; + } + + _etext = .; + + /* + * Start kernel read-write segment. + */ + READWRITE_SEGMENT_START + _sdata = .; + + .fardata : AT(ADDR(.fardata) - LOAD_OFFSET) + { + INIT_TASK_DATA(THREAD_SIZE) + NOSAVE_DATA + PAGE_ALIGNED_DATA(PAGE_SIZE) + CACHELINE_ALIGNED_DATA(128) + READ_MOSTLY_DATA(128) + DATA_DATA + CONSTRUCTORS + *(.data1) + *(.fardata .fardata.*) + *(.data.debug_bpt) + } + + .neardata ALIGN(8) : AT(ADDR(.neardata) - LOAD_OFFSET) + { + *(.neardata2 .neardata2.* .gnu.linkonce.s2.*) + *(.neardata .neardata.* .gnu.linkonce.s.*) + . = ALIGN(8); + } + + _edata = .; + + __bss_start = .; + SBSS(8) + BSS(8) + .far : + { + . = ALIGN(8); + *(.dynfar) + *(.far .far.* .gnu.linkonce.b.*) + . = ALIGN(8); + } + __bss_stop = .; + + _end = .; + + DWARF_DEBUG + + /DISCARD/ : + { + EXIT_TEXT + EXIT_DATA + EXIT_CALL + *(.discard) + *(.discard.*) + *(.interp) + } +} |