Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

25
arch/tile/kernel/Makefile Normal file
View File

@@ -0,0 +1,25 @@
#
# Makefile for the Linux/TILE kernel.
#
extra-y := vmlinux.lds head_$(BITS).o
obj-y := backtrace.o entry.o irq.o messaging.o \
pci-dma.o proc.o process.o ptrace.o reboot.o \
setup.o signal.o single_step.o stack.o sys.o \
sysfs.o time.o traps.o vdso.o \
intvec_$(BITS).o regs_$(BITS).o tile-desc_$(BITS).o
obj-$(CONFIG_HARDWALL) += hardwall.o
obj-$(CONFIG_COMPAT) += compat.o compat_signal.o
obj-$(CONFIG_SMP) += smpboot.o smp.o tlb.o
obj-$(CONFIG_MODULES) += module.o
obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel_$(BITS).o
ifdef CONFIG_TILEGX
obj-$(CONFIG_PCI) += pci_gx.o
else
obj-$(CONFIG_PCI) += pci.o
endif
obj-$(CONFIG_TILE_USB) += usb.o
obj-y += vdso/

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* Generates definitions from c-type structures used by assembly sources.
*/
#include <linux/kbuild.h>
#include <linux/thread_info.h>
#include <linux/sched.h>
#include <linux/hardirq.h>
#include <linux/ptrace.h>
#include <hv/hypervisor.h>
/* Check for compatible compiler early in the build. */
#ifdef CONFIG_TILEGX
# ifndef __tilegx__
# error Can only build TILE-Gx configurations with tilegx compiler
# endif
# ifndef __LP64__
# error Must not specify -m32 when building the TILE-Gx kernel
# endif
#else
# ifdef __tilegx__
# error Can not build TILEPro/TILE64 configurations with tilegx compiler
# endif
#endif
void foo(void)
{
DEFINE(SINGLESTEP_STATE_BUFFER_OFFSET, \
offsetof(struct single_step_state, buffer));
DEFINE(SINGLESTEP_STATE_FLAGS_OFFSET, \
offsetof(struct single_step_state, flags));
DEFINE(SINGLESTEP_STATE_ORIG_PC_OFFSET, \
offsetof(struct single_step_state, orig_pc));
DEFINE(SINGLESTEP_STATE_NEXT_PC_OFFSET, \
offsetof(struct single_step_state, next_pc));
DEFINE(SINGLESTEP_STATE_BRANCH_NEXT_PC_OFFSET, \
offsetof(struct single_step_state, branch_next_pc));
DEFINE(SINGLESTEP_STATE_UPDATE_VALUE_OFFSET, \
offsetof(struct single_step_state, update_value));
DEFINE(THREAD_INFO_TASK_OFFSET, \
offsetof(struct thread_info, task));
DEFINE(THREAD_INFO_FLAGS_OFFSET, \
offsetof(struct thread_info, flags));
DEFINE(THREAD_INFO_STATUS_OFFSET, \
offsetof(struct thread_info, status));
DEFINE(THREAD_INFO_HOMECACHE_CPU_OFFSET, \
offsetof(struct thread_info, homecache_cpu));
DEFINE(THREAD_INFO_STEP_STATE_OFFSET, \
offsetof(struct thread_info, step_state));
DEFINE(TASK_STRUCT_THREAD_KSP_OFFSET,
offsetof(struct task_struct, thread.ksp));
DEFINE(TASK_STRUCT_THREAD_PC_OFFSET,
offsetof(struct task_struct, thread.pc));
DEFINE(HV_TOPOLOGY_WIDTH_OFFSET, \
offsetof(HV_Topology, width));
DEFINE(HV_TOPOLOGY_HEIGHT_OFFSET, \
offsetof(HV_Topology, height));
DEFINE(IRQ_CPUSTAT_SYSCALL_COUNT_OFFSET, \
offsetof(irq_cpustat_t, irq_syscall_count));
}

View File

@@ -0,0 +1,683 @@
/*
* Copyright 2011 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/kernel.h>
#include <linux/string.h>
#include <asm/byteorder.h>
#include <asm/backtrace.h>
#include <asm/tile-desc.h>
#include <arch/abi.h>
#ifdef __tilegx__
#define TILE_MAX_INSTRUCTIONS_PER_BUNDLE TILEGX_MAX_INSTRUCTIONS_PER_BUNDLE
#define tile_decoded_instruction tilegx_decoded_instruction
#define tile_mnemonic tilegx_mnemonic
#define parse_insn_tile parse_insn_tilegx
#define TILE_OPC_IRET TILEGX_OPC_IRET
#define TILE_OPC_ADDI TILEGX_OPC_ADDI
#define TILE_OPC_ADDLI TILEGX_OPC_ADDLI
#define TILE_OPC_INFO TILEGX_OPC_INFO
#define TILE_OPC_INFOL TILEGX_OPC_INFOL
#define TILE_OPC_JRP TILEGX_OPC_JRP
#define TILE_OPC_MOVE TILEGX_OPC_MOVE
#define OPCODE_STORE TILEGX_OPC_ST
typedef long long bt_int_reg_t;
#else
#define TILE_MAX_INSTRUCTIONS_PER_BUNDLE TILEPRO_MAX_INSTRUCTIONS_PER_BUNDLE
#define tile_decoded_instruction tilepro_decoded_instruction
#define tile_mnemonic tilepro_mnemonic
#define parse_insn_tile parse_insn_tilepro
#define TILE_OPC_IRET TILEPRO_OPC_IRET
#define TILE_OPC_ADDI TILEPRO_OPC_ADDI
#define TILE_OPC_ADDLI TILEPRO_OPC_ADDLI
#define TILE_OPC_INFO TILEPRO_OPC_INFO
#define TILE_OPC_INFOL TILEPRO_OPC_INFOL
#define TILE_OPC_JRP TILEPRO_OPC_JRP
#define TILE_OPC_MOVE TILEPRO_OPC_MOVE
#define OPCODE_STORE TILEPRO_OPC_SW
typedef int bt_int_reg_t;
#endif
/* A decoded bundle used for backtracer analysis. */
struct BacktraceBundle {
tile_bundle_bits bits;
int num_insns;
struct tile_decoded_instruction
insns[TILE_MAX_INSTRUCTIONS_PER_BUNDLE];
};
/* Locates an instruction inside the given bundle that
* has the specified mnemonic, and whose first 'num_operands_to_match'
* operands exactly match those in 'operand_values'.
*/
static const struct tile_decoded_instruction *find_matching_insn(
const struct BacktraceBundle *bundle,
tile_mnemonic mnemonic,
const int *operand_values,
int num_operands_to_match)
{
int i, j;
bool match;
for (i = 0; i < bundle->num_insns; i++) {
const struct tile_decoded_instruction *insn =
&bundle->insns[i];
if (insn->opcode->mnemonic != mnemonic)
continue;
match = true;
for (j = 0; j < num_operands_to_match; j++) {
if (operand_values[j] != insn->operand_values[j]) {
match = false;
break;
}
}
if (match)
return insn;
}
return NULL;
}
/* Does this bundle contain an 'iret' instruction? */
static inline bool bt_has_iret(const struct BacktraceBundle *bundle)
{
return find_matching_insn(bundle, TILE_OPC_IRET, NULL, 0) != NULL;
}
/* Does this bundle contain an 'addi sp, sp, OFFSET' or
* 'addli sp, sp, OFFSET' instruction, and if so, what is OFFSET?
*/
static bool bt_has_addi_sp(const struct BacktraceBundle *bundle, int *adjust)
{
static const int vals[2] = { TREG_SP, TREG_SP };
const struct tile_decoded_instruction *insn =
find_matching_insn(bundle, TILE_OPC_ADDI, vals, 2);
if (insn == NULL)
insn = find_matching_insn(bundle, TILE_OPC_ADDLI, vals, 2);
#ifdef __tilegx__
if (insn == NULL)
insn = find_matching_insn(bundle, TILEGX_OPC_ADDXLI, vals, 2);
if (insn == NULL)
insn = find_matching_insn(bundle, TILEGX_OPC_ADDXI, vals, 2);
#endif
if (insn == NULL)
return false;
*adjust = insn->operand_values[2];
return true;
}
/* Does this bundle contain any 'info OP' or 'infol OP'
* instruction, and if so, what are their OP? Note that OP is interpreted
* as an unsigned value by this code since that's what the caller wants.
* Returns the number of info ops found.
*/
static int bt_get_info_ops(const struct BacktraceBundle *bundle,
int operands[MAX_INFO_OPS_PER_BUNDLE])
{
int num_ops = 0;
int i;
for (i = 0; i < bundle->num_insns; i++) {
const struct tile_decoded_instruction *insn =
&bundle->insns[i];
if (insn->opcode->mnemonic == TILE_OPC_INFO ||
insn->opcode->mnemonic == TILE_OPC_INFOL) {
operands[num_ops++] = insn->operand_values[0];
}
}
return num_ops;
}
/* Does this bundle contain a jrp instruction, and if so, to which
* register is it jumping?
*/
static bool bt_has_jrp(const struct BacktraceBundle *bundle, int *target_reg)
{
const struct tile_decoded_instruction *insn =
find_matching_insn(bundle, TILE_OPC_JRP, NULL, 0);
if (insn == NULL)
return false;
*target_reg = insn->operand_values[0];
return true;
}
/* Does this bundle modify the specified register in any way? */
static bool bt_modifies_reg(const struct BacktraceBundle *bundle, int reg)
{
int i, j;
for (i = 0; i < bundle->num_insns; i++) {
const struct tile_decoded_instruction *insn =
&bundle->insns[i];
if (insn->opcode->implicitly_written_register == reg)
return true;
for (j = 0; j < insn->opcode->num_operands; j++)
if (insn->operands[j]->is_dest_reg &&
insn->operand_values[j] == reg)
return true;
}
return false;
}
/* Does this bundle modify sp? */
static inline bool bt_modifies_sp(const struct BacktraceBundle *bundle)
{
return bt_modifies_reg(bundle, TREG_SP);
}
/* Does this bundle modify lr? */
static inline bool bt_modifies_lr(const struct BacktraceBundle *bundle)
{
return bt_modifies_reg(bundle, TREG_LR);
}
/* Does this bundle contain the instruction 'move fp, sp'? */
static inline bool bt_has_move_r52_sp(const struct BacktraceBundle *bundle)
{
static const int vals[2] = { 52, TREG_SP };
return find_matching_insn(bundle, TILE_OPC_MOVE, vals, 2) != NULL;
}
/* Does this bundle contain a store of lr to sp? */
static inline bool bt_has_sw_sp_lr(const struct BacktraceBundle *bundle)
{
static const int vals[2] = { TREG_SP, TREG_LR };
return find_matching_insn(bundle, OPCODE_STORE, vals, 2) != NULL;
}
#ifdef __tilegx__
/* Track moveli values placed into registers. */
static inline void bt_update_moveli(const struct BacktraceBundle *bundle,
int moveli_args[])
{
int i;
for (i = 0; i < bundle->num_insns; i++) {
const struct tile_decoded_instruction *insn =
&bundle->insns[i];
if (insn->opcode->mnemonic == TILEGX_OPC_MOVELI) {
int reg = insn->operand_values[0];
moveli_args[reg] = insn->operand_values[1];
}
}
}
/* Does this bundle contain an 'add sp, sp, reg' instruction
* from a register that we saw a moveli into, and if so, what
* is the value in the register?
*/
static bool bt_has_add_sp(const struct BacktraceBundle *bundle, int *adjust,
int moveli_args[])
{
static const int vals[2] = { TREG_SP, TREG_SP };
const struct tile_decoded_instruction *insn =
find_matching_insn(bundle, TILEGX_OPC_ADDX, vals, 2);
if (insn) {
int reg = insn->operand_values[2];
if (moveli_args[reg]) {
*adjust = moveli_args[reg];
return true;
}
}
return false;
}
#endif
/* Locates the caller's PC and SP for a program starting at the
* given address.
*/
static void find_caller_pc_and_caller_sp(CallerLocation *location,
const unsigned long start_pc,
BacktraceMemoryReader read_memory_func,
void *read_memory_func_extra)
{
/* Have we explicitly decided what the sp is,
* rather than just the default?
*/
bool sp_determined = false;
/* Has any bundle seen so far modified lr? */
bool lr_modified = false;
/* Have we seen a move from sp to fp? */
bool sp_moved_to_r52 = false;
/* Have we seen a terminating bundle? */
bool seen_terminating_bundle = false;
/* Cut down on round-trip reading overhead by reading several
* bundles at a time.
*/
tile_bundle_bits prefetched_bundles[32];
int num_bundles_prefetched = 0;
int next_bundle = 0;
unsigned long pc;
#ifdef __tilegx__
/* Naively try to track moveli values to support addx for -m32. */
int moveli_args[TILEGX_NUM_REGISTERS] = { 0 };
#endif
/* Default to assuming that the caller's sp is the current sp.
* This is necessary to handle the case where we start backtracing
* right at the end of the epilog.
*/
location->sp_location = SP_LOC_OFFSET;
location->sp_offset = 0;
/* Default to having no idea where the caller PC is. */
location->pc_location = PC_LOC_UNKNOWN;
/* Don't even try if the PC is not aligned. */
if (start_pc % TILE_BUNDLE_ALIGNMENT_IN_BYTES != 0)
return;
for (pc = start_pc;; pc += sizeof(tile_bundle_bits)) {
struct BacktraceBundle bundle;
int num_info_ops, info_operands[MAX_INFO_OPS_PER_BUNDLE];
int one_ago, jrp_reg;
bool has_jrp;
if (next_bundle >= num_bundles_prefetched) {
/* Prefetch some bytes, but don't cross a page
* boundary since that might cause a read failure we
* don't care about if we only need the first few
* bytes. Note: we don't care what the actual page
* size is; using the minimum possible page size will
* prevent any problems.
*/
unsigned int bytes_to_prefetch = 4096 - (pc & 4095);
if (bytes_to_prefetch > sizeof prefetched_bundles)
bytes_to_prefetch = sizeof prefetched_bundles;
if (!read_memory_func(prefetched_bundles, pc,
bytes_to_prefetch,
read_memory_func_extra)) {
if (pc == start_pc) {
/* The program probably called a bad
* address, such as a NULL pointer.
* So treat this as if we are at the
* start of the function prolog so the
* backtrace will show how we got here.
*/
location->pc_location = PC_LOC_IN_LR;
return;
}
/* Unreadable address. Give up. */
break;
}
next_bundle = 0;
num_bundles_prefetched =
bytes_to_prefetch / sizeof(tile_bundle_bits);
}
/*
* Decode the next bundle.
* TILE always stores instruction bundles in little-endian
* mode, even when the chip is running in big-endian mode.
*/
bundle.bits = le64_to_cpu(prefetched_bundles[next_bundle++]);
bundle.num_insns =
parse_insn_tile(bundle.bits, pc, bundle.insns);
num_info_ops = bt_get_info_ops(&bundle, info_operands);
/* First look at any one_ago info ops if they are interesting,
* since they should shadow any non-one-ago info ops.
*/
for (one_ago = (pc != start_pc) ? 1 : 0;
one_ago >= 0; one_ago--) {
int i;
for (i = 0; i < num_info_ops; i++) {
int info_operand = info_operands[i];
if (info_operand < CALLER_UNKNOWN_BASE) {
/* Weird; reserved value, ignore it. */
continue;
}
/* Skip info ops which are not in the
* "one_ago" mode we want right now.
*/
if (((info_operand & ONE_BUNDLE_AGO_FLAG) != 0)
!= (one_ago != 0))
continue;
/* Clear the flag to make later checking
* easier. */
info_operand &= ~ONE_BUNDLE_AGO_FLAG;
/* Default to looking at PC_IN_LR_FLAG. */
if (info_operand & PC_IN_LR_FLAG)
location->pc_location =
PC_LOC_IN_LR;
else
location->pc_location =
PC_LOC_ON_STACK;
switch (info_operand) {
case CALLER_UNKNOWN_BASE:
location->pc_location = PC_LOC_UNKNOWN;
location->sp_location = SP_LOC_UNKNOWN;
return;
case CALLER_SP_IN_R52_BASE:
case CALLER_SP_IN_R52_BASE | PC_IN_LR_FLAG:
location->sp_location = SP_LOC_IN_R52;
return;
default:
{
const unsigned int val = info_operand
- CALLER_SP_OFFSET_BASE;
const unsigned int sp_offset =
(val >> NUM_INFO_OP_FLAGS) * 8;
if (sp_offset < 32768) {
/* This is a properly encoded
* SP offset. */
location->sp_location =
SP_LOC_OFFSET;
location->sp_offset =
sp_offset;
return;
} else {
/* This looked like an SP
* offset, but it's outside
* the legal range, so this
* must be an unrecognized
* info operand. Ignore it.
*/
}
}
break;
}
}
}
if (seen_terminating_bundle) {
/* We saw a terminating bundle during the previous
* iteration, so we were only looking for an info op.
*/
break;
}
if (bundle.bits == 0) {
/* Wacky terminating bundle. Stop looping, and hope
* we've already seen enough to find the caller.
*/
break;
}
/*
* Try to determine caller's SP.
*/
if (!sp_determined) {
int adjust;
if (bt_has_addi_sp(&bundle, &adjust)
#ifdef __tilegx__
|| bt_has_add_sp(&bundle, &adjust, moveli_args)
#endif
) {
location->sp_location = SP_LOC_OFFSET;
if (adjust <= 0) {
/* We are in prolog about to adjust
* SP. */
location->sp_offset = 0;
} else {
/* We are in epilog restoring SP. */
location->sp_offset = adjust;
}
sp_determined = true;
} else {
if (bt_has_move_r52_sp(&bundle)) {
/* Maybe in prolog, creating an
* alloca-style frame. But maybe in
* the middle of a fixed-size frame
* clobbering r52 with SP.
*/
sp_moved_to_r52 = true;
}
if (bt_modifies_sp(&bundle)) {
if (sp_moved_to_r52) {
/* We saw SP get saved into
* r52 earlier (or now), which
* must have been in the
* prolog, so we now know that
* SP is still holding the
* caller's sp value.
*/
location->sp_location =
SP_LOC_OFFSET;
location->sp_offset = 0;
} else {
/* Someone must have saved
* aside the caller's SP value
* into r52, so r52 holds the
* current value.
*/
location->sp_location =
SP_LOC_IN_R52;
}
sp_determined = true;
}
}
#ifdef __tilegx__
/* Track moveli arguments for -m32 mode. */
bt_update_moveli(&bundle, moveli_args);
#endif
}
if (bt_has_iret(&bundle)) {
/* This is a terminating bundle. */
seen_terminating_bundle = true;
continue;
}
/*
* Try to determine caller's PC.
*/
jrp_reg = -1;
has_jrp = bt_has_jrp(&bundle, &jrp_reg);
if (has_jrp)
seen_terminating_bundle = true;
if (location->pc_location == PC_LOC_UNKNOWN) {
if (has_jrp) {
if (jrp_reg == TREG_LR && !lr_modified) {
/* Looks like a leaf function, or else
* lr is already restored. */
location->pc_location =
PC_LOC_IN_LR;
} else {
location->pc_location =
PC_LOC_ON_STACK;
}
} else if (bt_has_sw_sp_lr(&bundle)) {
/* In prolog, spilling initial lr to stack. */
location->pc_location = PC_LOC_IN_LR;
} else if (bt_modifies_lr(&bundle)) {
lr_modified = true;
}
}
}
}
/* Initializes a backtracer to start from the given location.
*
* If the frame pointer cannot be determined it is set to -1.
*
* state: The state to be filled in.
* read_memory_func: A callback that reads memory.
* read_memory_func_extra: An arbitrary argument to read_memory_func.
* pc: The current PC.
* lr: The current value of the 'lr' register.
* sp: The current value of the 'sp' register.
* r52: The current value of the 'r52' register.
*/
void backtrace_init(BacktraceIterator *state,
BacktraceMemoryReader read_memory_func,
void *read_memory_func_extra,
unsigned long pc, unsigned long lr,
unsigned long sp, unsigned long r52)
{
CallerLocation location;
unsigned long fp, initial_frame_caller_pc;
/* Find out where we are in the initial frame. */
find_caller_pc_and_caller_sp(&location, pc,
read_memory_func, read_memory_func_extra);
switch (location.sp_location) {
case SP_LOC_UNKNOWN:
/* Give up. */
fp = -1;
break;
case SP_LOC_IN_R52:
fp = r52;
break;
case SP_LOC_OFFSET:
fp = sp + location.sp_offset;
break;
default:
/* Give up. */
fp = -1;
break;
}
/* If the frame pointer is not aligned to the basic word size
* something terrible happened and we should mark it as invalid.
*/
if (fp % sizeof(bt_int_reg_t) != 0)
fp = -1;
/* -1 means "don't know initial_frame_caller_pc". */
initial_frame_caller_pc = -1;
switch (location.pc_location) {
case PC_LOC_UNKNOWN:
/* Give up. */
fp = -1;
break;
case PC_LOC_IN_LR:
if (lr == 0 || lr % TILE_BUNDLE_ALIGNMENT_IN_BYTES != 0) {
/* Give up. */
fp = -1;
} else {
initial_frame_caller_pc = lr;
}
break;
case PC_LOC_ON_STACK:
/* Leave initial_frame_caller_pc as -1,
* meaning check the stack.
*/
break;
default:
/* Give up. */
fp = -1;
break;
}
state->pc = pc;
state->sp = sp;
state->fp = fp;
state->initial_frame_caller_pc = initial_frame_caller_pc;
state->read_memory_func = read_memory_func;
state->read_memory_func_extra = read_memory_func_extra;
}
/* Handle the case where the register holds more bits than the VA. */
static bool valid_addr_reg(bt_int_reg_t reg)
{
return ((unsigned long)reg == reg);
}
/* Advances the backtracing state to the calling frame, returning
* true iff successful.
*/
bool backtrace_next(BacktraceIterator *state)
{
unsigned long next_fp, next_pc;
bt_int_reg_t next_frame[2];
if (state->fp == -1) {
/* No parent frame. */
return false;
}
/* Try to read the frame linkage data chaining to the next function. */
if (!state->read_memory_func(&next_frame, state->fp, sizeof next_frame,
state->read_memory_func_extra)) {
return false;
}
next_fp = next_frame[1];
if (!valid_addr_reg(next_frame[1]) ||
next_fp % sizeof(bt_int_reg_t) != 0) {
/* Caller's frame pointer is suspect, so give up. */
return false;
}
if (state->initial_frame_caller_pc != -1) {
/* We must be in the initial stack frame and already know the
* caller PC.
*/
next_pc = state->initial_frame_caller_pc;
/* Force reading stack next time, in case we were in the
* initial frame. We don't do this above just to paranoidly
* avoid changing the struct at all when we return false.
*/
state->initial_frame_caller_pc = -1;
} else {
/* Get the caller PC from the frame linkage area. */
next_pc = next_frame[0];
if (!valid_addr_reg(next_frame[0]) || next_pc == 0 ||
next_pc % TILE_BUNDLE_ALIGNMENT_IN_BYTES != 0) {
/* The PC is suspect, so give up. */
return false;
}
}
/* Update state to become the caller's stack frame. */
state->pc = next_pc;
state->sp = state->fp;
state->fp = next_fp;
return true;
}

108
arch/tile/kernel/compat.c Normal file
View File

@@ -0,0 +1,108 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
/* Adjust unistd.h to provide 32-bit numbers and functions. */
#define __SYSCALL_COMPAT
#include <linux/compat.h>
#include <linux/syscalls.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/uaccess.h>
#include <linux/signal.h>
#include <asm/syscalls.h>
/*
* Syscalls that take 64-bit numbers traditionally take them in 32-bit
* "high" and "low" value parts on 32-bit architectures.
* In principle, one could imagine passing some register arguments as
* fully 64-bit on TILE-Gx in 32-bit mode, but it seems easier to
* adapt the usual convention.
*/
COMPAT_SYSCALL_DEFINE4(truncate64, char __user *, filename, u32, dummy,
u32, low, u32, high)
{
return sys_truncate(filename, ((loff_t)high << 32) | low);
}
COMPAT_SYSCALL_DEFINE4(ftruncate64, unsigned int, fd, u32, dummy,
u32, low, u32, high)
{
return sys_ftruncate(fd, ((loff_t)high << 32) | low);
}
COMPAT_SYSCALL_DEFINE6(pread64, unsigned int, fd, char __user *, ubuf,
size_t, count, u32, dummy, u32, low, u32, high)
{
return sys_pread64(fd, ubuf, count, ((loff_t)high << 32) | low);
}
COMPAT_SYSCALL_DEFINE6(pwrite64, unsigned int, fd, char __user *, ubuf,
size_t, count, u32, dummy, u32, low, u32, high)
{
return sys_pwrite64(fd, ubuf, count, ((loff_t)high << 32) | low);
}
COMPAT_SYSCALL_DEFINE6(sync_file_range2, int, fd, unsigned int, flags,
u32, offset_lo, u32, offset_hi,
u32, nbytes_lo, u32, nbytes_hi)
{
return sys_sync_file_range(fd, ((loff_t)offset_hi << 32) | offset_lo,
((loff_t)nbytes_hi << 32) | nbytes_lo,
flags);
}
COMPAT_SYSCALL_DEFINE6(fallocate, int, fd, int, mode,
u32, offset_lo, u32, offset_hi,
u32, len_lo, u32, len_hi)
{
return sys_fallocate(fd, mode, ((loff_t)offset_hi << 32) | offset_lo,
((loff_t)len_hi << 32) | len_lo);
}
/*
* Avoid bug in generic sys_llseek() that specifies offset_high and
* offset_low as "unsigned long", thus making it possible to pass
* a sign-extended high 32 bits in offset_low.
*/
COMPAT_SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned int, offset_high,
unsigned int, offset_low, loff_t __user *, result,
unsigned int, origin)
{
return sys_llseek(fd, offset_high, offset_low, result, origin);
}
/* Provide the compat syscall number to call mapping. */
#undef __SYSCALL
#define __SYSCALL(nr, call) [nr] = (call),
/* See comments in sys.c */
#define compat_sys_fadvise64_64 sys32_fadvise64_64
#define compat_sys_readahead sys32_readahead
#define sys_llseek compat_sys_llseek
/* Call the assembly trampolines where necessary. */
#define compat_sys_rt_sigreturn _compat_sys_rt_sigreturn
#define sys_clone _sys_clone
/*
* 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 *compat_sys_call_table[__NR_syscalls] = {
[0 ... __NR_syscalls-1] = sys_ni_syscall,
#include <asm/unistd.h>
};

View File

@@ -0,0 +1,255 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/wait.h>
#include <linux/unistd.h>
#include <linux/stddef.h>
#include <linux/personality.h>
#include <linux/suspend.h>
#include <linux/ptrace.h>
#include <linux/elf.h>
#include <linux/compat.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <asm/processor.h>
#include <asm/ucontext.h>
#include <asm/sigframe.h>
#include <asm/syscalls.h>
#include <asm/vdso.h>
#include <arch/interrupts.h>
struct compat_ucontext {
compat_ulong_t uc_flags;
compat_uptr_t uc_link;
struct compat_sigaltstack uc_stack;
struct sigcontext uc_mcontext;
sigset_t uc_sigmask; /* mask last for extensibility */
};
struct compat_rt_sigframe {
unsigned char save_area[C_ABI_SAVE_AREA_SIZE]; /* caller save area */
struct compat_siginfo info;
struct compat_ucontext uc;
};
int copy_siginfo_to_user32(struct compat_siginfo __user *to, siginfo_t *from)
{
int err;
if (!access_ok(VERIFY_WRITE, to, sizeof(struct compat_siginfo)))
return -EFAULT;
/* If you change siginfo_t structure, please make sure that
this code is fixed accordingly.
It should never copy any pad contained in the structure
to avoid security leaks, but must copy the generic
3 ints plus the relevant union member. */
err = __put_user(from->si_signo, &to->si_signo);
err |= __put_user(from->si_errno, &to->si_errno);
err |= __put_user((short)from->si_code, &to->si_code);
if (from->si_code < 0) {
err |= __put_user(from->si_pid, &to->si_pid);
err |= __put_user(from->si_uid, &to->si_uid);
err |= __put_user(ptr_to_compat(from->si_ptr), &to->si_ptr);
} else {
/*
* First 32bits of unions are always present:
* si_pid === si_band === si_tid === si_addr(LS half)
*/
err |= __put_user(from->_sifields._pad[0],
&to->_sifields._pad[0]);
switch (from->si_code >> 16) {
case __SI_FAULT >> 16:
break;
case __SI_CHLD >> 16:
err |= __put_user(from->si_utime, &to->si_utime);
err |= __put_user(from->si_stime, &to->si_stime);
err |= __put_user(from->si_status, &to->si_status);
/* FALL THROUGH */
default:
case __SI_KILL >> 16:
err |= __put_user(from->si_uid, &to->si_uid);
break;
case __SI_POLL >> 16:
err |= __put_user(from->si_fd, &to->si_fd);
break;
case __SI_TIMER >> 16:
err |= __put_user(from->si_overrun, &to->si_overrun);
err |= __put_user(ptr_to_compat(from->si_ptr),
&to->si_ptr);
break;
/* This is not generated by the kernel as of now. */
case __SI_RT >> 16:
case __SI_MESGQ >> 16:
err |= __put_user(from->si_uid, &to->si_uid);
err |= __put_user(from->si_int, &to->si_int);
break;
}
}
return err;
}
int copy_siginfo_from_user32(siginfo_t *to, struct compat_siginfo __user *from)
{
int err;
u32 ptr32;
if (!access_ok(VERIFY_READ, from, sizeof(struct compat_siginfo)))
return -EFAULT;
err = __get_user(to->si_signo, &from->si_signo);
err |= __get_user(to->si_errno, &from->si_errno);
err |= __get_user(to->si_code, &from->si_code);
err |= __get_user(to->si_pid, &from->si_pid);
err |= __get_user(to->si_uid, &from->si_uid);
err |= __get_user(ptr32, &from->si_ptr);
to->si_ptr = compat_ptr(ptr32);
return err;
}
/* The assembly shim for this function arranges to ignore the return value. */
long compat_sys_rt_sigreturn(void)
{
struct pt_regs *regs = current_pt_regs();
struct compat_rt_sigframe __user *frame =
(struct compat_rt_sigframe __user *) compat_ptr(regs->sp);
sigset_t set;
if (!access_ok(VERIFY_READ, frame, sizeof(*frame)))
goto badframe;
if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set)))
goto badframe;
set_current_blocked(&set);
if (restore_sigcontext(regs, &frame->uc.uc_mcontext))
goto badframe;
if (compat_restore_altstack(&frame->uc.uc_stack))
goto badframe;
return 0;
badframe:
signal_fault("bad sigreturn frame", regs, frame, 0);
return 0;
}
/*
* Determine which stack to use..
*/
static inline void __user *compat_get_sigframe(struct k_sigaction *ka,
struct pt_regs *regs,
size_t frame_size)
{
unsigned long sp;
/* Default to using normal stack */
sp = (unsigned long)compat_ptr(regs->sp);
/*
* If we are on the alternate signal stack and would overflow
* it, don't. Return an always-bogus address instead so we
* will die with SIGSEGV.
*/
if (on_sig_stack(sp) && !likely(on_sig_stack(sp - frame_size)))
return (void __user __force *)-1UL;
/* This is the X/Open sanctioned signal stack switching. */
if (ka->sa.sa_flags & SA_ONSTACK) {
if (sas_ss_flags(sp) == 0)
sp = current->sas_ss_sp + current->sas_ss_size;
}
sp -= frame_size;
/*
* Align the stack pointer according to the TILE ABI,
* i.e. so that on function entry (sp & 15) == 0.
*/
sp &= -16UL;
return (void __user *) sp;
}
int compat_setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
sigset_t *set, struct pt_regs *regs)
{
unsigned long restorer;
struct compat_rt_sigframe __user *frame;
int err = 0;
int usig;
frame = compat_get_sigframe(ka, regs, sizeof(*frame));
if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
goto give_sigsegv;
usig = current_thread_info()->exec_domain
&& current_thread_info()->exec_domain->signal_invmap
&& sig < 32
? current_thread_info()->exec_domain->signal_invmap[sig]
: sig;
/* Always write at least the signal number for the stack backtracer. */
if (ka->sa.sa_flags & SA_SIGINFO) {
/* At sigreturn time, restore the callee-save registers too. */
err |= copy_siginfo_to_user32(&frame->info, info);
regs->flags |= PT_FLAGS_RESTORE_REGS;
} else {
err |= __put_user(info->si_signo, &frame->info.si_signo);
}
/* Create the ucontext. */
err |= __clear_user(&frame->save_area, sizeof(frame->save_area));
err |= __put_user(0, &frame->uc.uc_flags);
err |= __put_user(0, &frame->uc.uc_link);
err |= __compat_save_altstack(&frame->uc.uc_stack, regs->sp);
err |= setup_sigcontext(&frame->uc.uc_mcontext, regs);
err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
if (err)
goto give_sigsegv;
restorer = VDSO_SYM(&__vdso_rt_sigreturn);
if (ka->sa.sa_flags & SA_RESTORER)
restorer = ptr_to_compat_reg(ka->sa.sa_restorer);
/*
* Set up registers for signal handler.
* Registers that we don't modify keep the value they had from
* user-space at the time we took the signal.
* We always pass siginfo and mcontext, regardless of SA_SIGINFO,
* since some things rely on this (e.g. glibc's debug/segfault.c).
*/
regs->pc = ptr_to_compat_reg(ka->sa.sa_handler);
regs->ex1 = PL_ICS_EX1(USER_PL, 1); /* set crit sec in handler */
regs->sp = ptr_to_compat_reg(frame);
regs->lr = restorer;
regs->regs[0] = (unsigned long) usig;
regs->regs[1] = ptr_to_compat_reg(&frame->info);
regs->regs[2] = ptr_to_compat_reg(&frame->uc);
regs->flags |= PT_FLAGS_CALLER_SAVES;
return 0;
give_sigsegv:
signal_fault("bad setup frame", regs, frame, sig);
return -EFAULT;
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/console.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/irqflags.h>
#include <linux/printk.h>
#include <asm/setup.h>
#include <hv/hypervisor.h>
static void early_hv_write(struct console *con, const char *s, unsigned n)
{
hv_console_write((HV_VirtAddr) s, n);
}
static struct console early_hv_console = {
.name = "earlyhv",
.write = early_hv_write,
.flags = CON_PRINTBUFFER,
.index = -1,
};
/* Direct interface for emergencies */
static int early_console_complete;
void early_panic(const char *fmt, ...)
{
va_list ap;
arch_local_irq_disable_all();
va_start(ap, fmt);
early_printk("Kernel panic - not syncing: ");
early_vprintk(fmt, ap);
early_console->write(early_console, "\n", 1);
va_end(ap);
dump_stack();
hv_halt();
}
static int __initdata keep_early;
static int __init setup_early_printk(char *str)
{
if (early_console)
return 1;
if (str != NULL && strncmp(str, "keep", 4) == 0)
keep_early = 1;
early_console = &early_hv_console;
register_console(early_console);
return 0;
}
void __init disable_early_printk(void)
{
early_console_complete = 1;
if (!early_console)
return;
if (!keep_early) {
early_printk("disabling early console\n");
unregister_console(early_console);
early_console = NULL;
} else {
early_printk("keeping early console\n");
}
}
void warn_early_printk(void)
{
if (early_console_complete || early_console)
return;
early_printk("\
Machine shutting down before console output is fully initialized.\n\
You may wish to reboot and add the option 'earlyprintk' to your\n\
boot command line to see any diagnostic early console output.\n\
");
}
early_param("earlyprintk", setup_early_printk);

71
arch/tile/kernel/entry.S Normal file
View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/linkage.h>
#include <linux/unistd.h>
#include <asm/irqflags.h>
#include <asm/processor.h>
#include <arch/abi.h>
#include <arch/spr_def.h>
#ifdef __tilegx__
#define bnzt bnezt
#endif
STD_ENTRY(current_text_addr)
{ move r0, lr; jrp lr }
STD_ENDPROC(current_text_addr)
STD_ENTRY(dump_stack)
{ move r2, lr; lnk r1 }
{ move r4, r52; addli r1, r1, dump_stack - . }
{ move r3, sp; j _dump_stack }
jrp lr /* keep backtracer happy */
STD_ENDPROC(dump_stack)
STD_ENTRY(KBacktraceIterator_init_current)
{ move r2, lr; lnk r1 }
{ move r4, r52; addli r1, r1, KBacktraceIterator_init_current - . }
{ move r3, sp; j _KBacktraceIterator_init_current }
jrp lr /* keep backtracer happy */
STD_ENDPROC(KBacktraceIterator_init_current)
/* Loop forever on a nap during SMP boot. */
STD_ENTRY(smp_nap)
nap
nop /* avoid provoking the icache prefetch with a jump */
j smp_nap /* we are not architecturally guaranteed not to exit nap */
jrp lr /* clue in the backtracer */
STD_ENDPROC(smp_nap)
/*
* Enable interrupts racelessly and then nap until interrupted.
* Architecturally, we are guaranteed that enabling interrupts via
* mtspr to INTERRUPT_CRITICAL_SECTION only interrupts at the next PC.
* This function's _cpu_idle_nap address is special; see intvec.S.
* When interrupted at _cpu_idle_nap, we bump the PC forward 8, and
* as a result return to the function that called _cpu_idle().
*/
STD_ENTRY(_cpu_idle)
movei r1, 1
IRQ_ENABLE_LOAD(r2, r3)
mtspr INTERRUPT_CRITICAL_SECTION, r1
IRQ_ENABLE_APPLY(r2, r3) /* unmask, but still with ICS set */
mtspr INTERRUPT_CRITICAL_SECTION, zero
.global _cpu_idle_nap
_cpu_idle_nap:
nap
nop /* avoid provoking the icache prefetch with a jump */
jrp lr
STD_ENDPROC(_cpu_idle)

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2011 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* Atomically access user memory, but use MMU to avoid propagating
* kernel exceptions.
*/
#include <linux/linkage.h>
#include <asm/errno.h>
#include <asm/futex.h>
#include <asm/page.h>
#include <asm/processor.h>
/*
* Provide a set of atomic memory operations supporting <asm/futex.h>.
*
* r0: user address to manipulate
* r1: new value to write, or for cmpxchg, old value to compare against
* r2: (cmpxchg only) new value to write
*
* Return __get_user struct, r0 with value, r1 with error.
*/
#define FUTEX_OP(name, ...) \
STD_ENTRY(futex_##name) \
__VA_ARGS__; \
{ \
move r1, zero; \
jrp lr \
}; \
STD_ENDPROC(futex_##name); \
.pushsection __ex_table,"a"; \
.quad 1b, get_user_fault; \
.popsection
.pushsection .fixup,"ax"
get_user_fault:
{ movei r1, -EFAULT; jrp lr }
ENDPROC(get_user_fault)
.popsection
FUTEX_OP(cmpxchg, mtspr CMPEXCH_VALUE, r1; 1: cmpexch4 r0, r0, r2)
FUTEX_OP(set, 1: exch4 r0, r0, r1)
FUTEX_OP(add, 1: fetchadd4 r0, r0, r1)
FUTEX_OP(or, 1: fetchor4 r0, r0, r1)
FUTEX_OP(andn, nor r1, r1, zero; 1: fetchand4 r0, r0, r1)

1093
arch/tile/kernel/hardwall.c Normal file

File diff suppressed because it is too large Load Diff

184
arch/tile/kernel/head_32.S Normal file
View File

@@ -0,0 +1,184 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* TILE startup code.
*/
#include <linux/linkage.h>
#include <linux/init.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/thread_info.h>
#include <asm/processor.h>
#include <asm/asm-offsets.h>
#include <hv/hypervisor.h>
#include <arch/chip.h>
#include <arch/spr_def.h>
/*
* This module contains the entry code for kernel images. It performs the
* minimal setup needed to call the generic C routines.
*/
__HEAD
ENTRY(_start)
/* Notify the hypervisor of what version of the API we want */
{
movei r1, TILE_CHIP
movei r2, TILE_CHIP_REV
}
{
moveli r0, _HV_VERSION_OLD_HV_INIT
jal hv_init
}
/* Get a reasonable default ASID in r0 */
{
move r0, zero
jal hv_inquire_asid
}
/* Install the default page table */
{
moveli r6, lo16(swapper_pgprot - PAGE_OFFSET)
move r4, r0 /* use starting ASID of range for this page table */
}
{
moveli r0, lo16(swapper_pg_dir - PAGE_OFFSET)
auli r6, r6, ha16(swapper_pgprot - PAGE_OFFSET)
}
{
lw r2, r6
addi r6, r6, 4
}
{
lw r3, r6
auli r0, r0, ha16(swapper_pg_dir - PAGE_OFFSET)
}
{
inv r6
move r1, zero /* high 32 bits of CPA is zero */
}
{
moveli lr, lo16(1f)
moveli r5, CTX_PAGE_FLAG
}
{
auli lr, lr, ha16(1f)
j hv_install_context
}
1:
/* Get our processor number and save it away in SAVE_K_0. */
jal hv_inquire_topology
mulll_uu r4, r1, r2 /* r1 == y, r2 == width */
add r4, r4, r0 /* r0 == x, so r4 == cpu == y*width + x */
#ifdef CONFIG_SMP
/*
* Load up our per-cpu offset. When the first (master) tile
* boots, this value is still zero, so we will load boot_pc
* with start_kernel, and boot_sp with init_stack + THREAD_SIZE.
* The master tile initializes the per-cpu offset array, so that
* when subsequent (secondary) tiles boot, they will instead load
* from their per-cpu versions of boot_sp and boot_pc.
*/
moveli r5, lo16(__per_cpu_offset)
auli r5, r5, ha16(__per_cpu_offset)
s2a r5, r4, r5
lw r5, r5
bnz r5, 1f
/*
* Save the width and height to the smp_topology variable
* for later use.
*/
moveli r0, lo16(smp_topology + HV_TOPOLOGY_WIDTH_OFFSET)
auli r0, r0, ha16(smp_topology + HV_TOPOLOGY_WIDTH_OFFSET)
{
sw r0, r2
addi r0, r0, (HV_TOPOLOGY_HEIGHT_OFFSET - HV_TOPOLOGY_WIDTH_OFFSET)
}
sw r0, r3
1:
#else
move r5, zero
#endif
/* Load and go with the correct pc and sp. */
{
addli r1, r5, lo16(boot_sp)
addli r0, r5, lo16(boot_pc)
}
{
auli r1, r1, ha16(boot_sp)
auli r0, r0, ha16(boot_pc)
}
lw r0, r0
lw sp, r1
or r4, sp, r4
mtspr SPR_SYSTEM_SAVE_K_0, r4 /* save ksp0 + cpu */
addi sp, sp, -STACK_TOP_DELTA
{
move lr, zero /* stop backtraces in the called function */
jr r0
}
ENDPROC(_start)
__PAGE_ALIGNED_BSS
.align PAGE_SIZE
ENTRY(empty_zero_page)
.fill PAGE_SIZE,1,0
END(empty_zero_page)
.macro PTE va, cpa, bits1, no_org=0
.ifeq \no_org
.org swapper_pg_dir + PGD_INDEX(\va) * HV_PTE_SIZE
.endif
.word HV_PTE_PAGE | HV_PTE_DIRTY | HV_PTE_PRESENT | HV_PTE_ACCESSED | \
(HV_PTE_MODE_CACHE_NO_L3 << HV_PTE_INDEX_MODE)
.word (\bits1) | (HV_CPA_TO_PTFN(\cpa) << (HV_PTE_INDEX_PTFN - 32))
.endm
__PAGE_ALIGNED_DATA
.align PAGE_SIZE
ENTRY(swapper_pg_dir)
/*
* All data pages from PAGE_OFFSET to MEM_USER_INTRPT are mapped as
* VA = PA + PAGE_OFFSET. We remap things with more precise access
* permissions and more respect for size of RAM later.
*/
.set addr, 0
.rept (MEM_USER_INTRPT - PAGE_OFFSET) >> PGDIR_SHIFT
PTE addr + PAGE_OFFSET, addr, (1 << (HV_PTE_INDEX_READABLE - 32)) | \
(1 << (HV_PTE_INDEX_WRITABLE - 32))
.set addr, addr + PGDIR_SIZE
.endr
/* The true text VAs are mapped as VA = PA + MEM_SV_INTRPT */
PTE MEM_SV_INTRPT, 0, (1 << (HV_PTE_INDEX_READABLE - 32)) | \
(1 << (HV_PTE_INDEX_EXECUTABLE - 32))
.org swapper_pg_dir + PGDIR_SIZE
END(swapper_pg_dir)
/*
* Isolate swapper_pgprot to its own cache line, since each cpu
* starting up will read it using VA-is-PA and local homing.
* This would otherwise likely conflict with other data on the cache
* line, once we have set its permanent home in the page tables.
*/
__INITDATA
.align CHIP_L2_LINE_SIZE()
ENTRY(swapper_pgprot)
PTE 0, 0, (1 << (HV_PTE_INDEX_READABLE - 32)) | \
(1 << (HV_PTE_INDEX_WRITABLE - 32)), 1
.align CHIP_L2_LINE_SIZE()
END(swapper_pgprot)

275
arch/tile/kernel/head_64.S Normal file
View File

@@ -0,0 +1,275 @@
/*
* Copyright 2011 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* TILE startup code.
*/
#include <linux/linkage.h>
#include <linux/init.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/thread_info.h>
#include <asm/processor.h>
#include <asm/asm-offsets.h>
#include <hv/hypervisor.h>
#include <arch/chip.h>
#include <arch/spr_def.h>
/*
* This module contains the entry code for kernel images. It performs the
* minimal setup needed to call the generic C routines.
*/
__HEAD
ENTRY(_start)
/* Notify the hypervisor of what version of the API we want */
{
#if KERNEL_PL == 1 && _HV_VERSION == 13
/* Support older hypervisors by asking for API version 12. */
movei r0, _HV_VERSION_OLD_HV_INIT
#else
movei r0, _HV_VERSION
#endif
movei r1, TILE_CHIP
}
{
movei r2, TILE_CHIP_REV
movei r3, KERNEL_PL
}
jal hv_init
/* Get a reasonable default ASID in r0 */
{
move r0, zero
jal hv_inquire_asid
}
/*
* Install the default page table. The relocation required to
* statically define the table is a bit too complex, so we have
* to plug in the pointer from the L0 to the L1 table by hand.
* We only do this on the first cpu to boot, though, since the
* other CPUs should see a properly-constructed page table.
*/
{
v4int_l r2, zero, r0 /* ASID for hv_install_context */
moveli r4, hw1_last(swapper_pgprot - PAGE_OFFSET)
}
{
shl16insli r4, r4, hw0(swapper_pgprot - PAGE_OFFSET)
}
{
ld r1, r4 /* access_pte for hv_install_context */
}
{
moveli r0, hw1_last(.Lsv_data_pmd - PAGE_OFFSET)
moveli r6, hw1_last(temp_data_pmd - PAGE_OFFSET)
}
{
/* After initializing swapper_pgprot, HV_PTE_GLOBAL is set. */
bfextu r7, r1, HV_PTE_INDEX_GLOBAL, HV_PTE_INDEX_GLOBAL
inv r4
}
bnez r7, .Lno_write
{
shl16insli r0, r0, hw0(.Lsv_data_pmd - PAGE_OFFSET)
shl16insli r6, r6, hw0(temp_data_pmd - PAGE_OFFSET)
}
{
/* Cut off the low bits of the PT address. */
shrui r6, r6, HV_LOG2_PAGE_TABLE_ALIGN
/* Start with our access pte. */
move r5, r1
}
{
/* Stuff the address into the page table pointer slot of the PTE. */
bfins r5, r6, HV_PTE_INDEX_PTFN, \
HV_PTE_INDEX_PTFN + HV_PTE_PTFN_BITS - 1
}
{
/* Store the L0 data PTE. */
st r0, r5
addli r6, r6, (temp_code_pmd - temp_data_pmd) >> \
HV_LOG2_PAGE_TABLE_ALIGN
}
{
addli r0, r0, .Lsv_code_pmd - .Lsv_data_pmd
bfins r5, r6, HV_PTE_INDEX_PTFN, \
HV_PTE_INDEX_PTFN + HV_PTE_PTFN_BITS - 1
}
/* Store the L0 code PTE. */
st r0, r5
.Lno_write:
moveli lr, hw2_last(1f)
{
shl16insli lr, lr, hw1(1f)
moveli r0, hw1_last(swapper_pg_dir - PAGE_OFFSET)
}
{
shl16insli lr, lr, hw0(1f)
shl16insli r0, r0, hw0(swapper_pg_dir - PAGE_OFFSET)
}
{
moveli r3, CTX_PAGE_FLAG
j hv_install_context
}
1:
/* Install the interrupt base. */
moveli r0, hw2_last(MEM_SV_START)
shl16insli r0, r0, hw1(MEM_SV_START)
shl16insli r0, r0, hw0(MEM_SV_START)
mtspr SPR_INTERRUPT_VECTOR_BASE_K, r0
/*
* Get our processor number and save it away in SAVE_K_0.
* Extract stuff from the topology structure: r4 = y, r6 = x,
* r5 = width. FIXME: consider whether we want to just make these
* 64-bit values (and if so fix smp_topology write below, too).
*/
jal hv_inquire_topology
{
v4int_l r5, zero, r1 /* r5 = width */
shrui r4, r0, 32 /* r4 = y */
}
{
v4int_l r6, zero, r0 /* r6 = x */
mul_lu_lu r4, r4, r5
}
{
add r4, r4, r6 /* r4 == cpu == y*width + x */
}
#ifdef CONFIG_SMP
/*
* Load up our per-cpu offset. When the first (master) tile
* boots, this value is still zero, so we will load boot_pc
* with start_kernel, and boot_sp with init_stack + THREAD_SIZE.
* The master tile initializes the per-cpu offset array, so that
* when subsequent (secondary) tiles boot, they will instead load
* from their per-cpu versions of boot_sp and boot_pc.
*/
moveli r5, hw2_last(__per_cpu_offset)
shl16insli r5, r5, hw1(__per_cpu_offset)
shl16insli r5, r5, hw0(__per_cpu_offset)
shl3add r5, r4, r5
ld r5, r5
bnez r5, 1f
/*
* Save the width and height to the smp_topology variable
* for later use.
*/
moveli r0, hw2_last(smp_topology + HV_TOPOLOGY_WIDTH_OFFSET)
shl16insli r0, r0, hw1(smp_topology + HV_TOPOLOGY_WIDTH_OFFSET)
shl16insli r0, r0, hw0(smp_topology + HV_TOPOLOGY_WIDTH_OFFSET)
st r0, r1
1:
#else
move r5, zero
#endif
/* Load and go with the correct pc and sp. */
{
moveli r1, hw2_last(boot_sp)
moveli r0, hw2_last(boot_pc)
}
{
shl16insli r1, r1, hw1(boot_sp)
shl16insli r0, r0, hw1(boot_pc)
}
{
shl16insli r1, r1, hw0(boot_sp)
shl16insli r0, r0, hw0(boot_pc)
}
{
add r1, r1, r5
add r0, r0, r5
}
ld r0, r0
ld sp, r1
or r4, sp, r4
mtspr SPR_SYSTEM_SAVE_K_0, r4 /* save ksp0 + cpu */
addi sp, sp, -STACK_TOP_DELTA
{
move lr, zero /* stop backtraces in the called function */
jr r0
}
ENDPROC(_start)
__PAGE_ALIGNED_BSS
.align PAGE_SIZE
ENTRY(empty_zero_page)
.fill PAGE_SIZE,1,0
END(empty_zero_page)
.macro PTE cpa, bits1
.quad HV_PTE_PAGE | HV_PTE_DIRTY | HV_PTE_PRESENT | HV_PTE_ACCESSED |\
HV_PTE_GLOBAL | (HV_PTE_MODE_CACHE_NO_L3 << HV_PTE_INDEX_MODE) |\
(\bits1) | (HV_CPA_TO_PTFN(\cpa) << HV_PTE_INDEX_PTFN)
.endm
__PAGE_ALIGNED_DATA
.align PAGE_SIZE
ENTRY(swapper_pg_dir)
.org swapper_pg_dir + PGD_INDEX(PAGE_OFFSET) * HV_PTE_SIZE
.Lsv_data_pmd:
.quad 0 /* PTE temp_data_pmd - PAGE_OFFSET, 0 */
.org swapper_pg_dir + PGD_INDEX(MEM_SV_START) * HV_PTE_SIZE
.Lsv_code_pmd:
.quad 0 /* PTE temp_code_pmd - PAGE_OFFSET, 0 */
.org swapper_pg_dir + SIZEOF_PGD
END(swapper_pg_dir)
.align HV_PAGE_TABLE_ALIGN
ENTRY(temp_data_pmd)
/*
* We fill the PAGE_OFFSET pmd with huge pages with
* VA = PA + PAGE_OFFSET. We remap things with more precise access
* permissions later.
*/
.set addr, 0
.rept PTRS_PER_PMD
PTE addr, HV_PTE_READABLE | HV_PTE_WRITABLE
.set addr, addr + HPAGE_SIZE
.endr
.org temp_data_pmd + SIZEOF_PMD
END(temp_data_pmd)
.align HV_PAGE_TABLE_ALIGN
ENTRY(temp_code_pmd)
/*
* We fill the MEM_SV_START pmd with huge pages with
* VA = PA + PAGE_OFFSET. We remap things with more precise access
* permissions later.
*/
.set addr, 0
.rept PTRS_PER_PMD
PTE addr, HV_PTE_READABLE | HV_PTE_EXECUTABLE
.set addr, addr + HPAGE_SIZE
.endr
.org temp_code_pmd + SIZEOF_PMD
END(temp_code_pmd)
/*
* Isolate swapper_pgprot to its own cache line, since each cpu
* starting up will read it using VA-is-PA and local homing.
* This would otherwise likely conflict with other data on the cache
* line, once we have set its permanent home in the page tables.
*/
__INITDATA
.align CHIP_L2_LINE_SIZE()
ENTRY(swapper_pgprot)
.quad HV_PTE_PRESENT | (HV_PTE_MODE_CACHE_NO_L3 << HV_PTE_INDEX_MODE)
.align CHIP_L2_LINE_SIZE()
END(swapper_pgprot)

View File

@@ -0,0 +1,59 @@
/* Hypervisor call vector addresses; see <hv/hypervisor.h> */
hv_init = TEXT_OFFSET + 0x10020;
hv_install_context = TEXT_OFFSET + 0x10040;
hv_sysconf = TEXT_OFFSET + 0x10060;
hv_get_rtc = TEXT_OFFSET + 0x10080;
hv_set_rtc = TEXT_OFFSET + 0x100a0;
hv_flush_asid = TEXT_OFFSET + 0x100c0;
hv_flush_page = TEXT_OFFSET + 0x100e0;
hv_flush_pages = TEXT_OFFSET + 0x10100;
hv_restart = TEXT_OFFSET + 0x10120;
hv_halt = TEXT_OFFSET + 0x10140;
hv_power_off = TEXT_OFFSET + 0x10160;
hv_inquire_physical = TEXT_OFFSET + 0x10180;
hv_inquire_memory_controller = TEXT_OFFSET + 0x101a0;
hv_inquire_virtual = TEXT_OFFSET + 0x101c0;
hv_inquire_asid = TEXT_OFFSET + 0x101e0;
hv_nanosleep = TEXT_OFFSET + 0x10200;
hv_console_read_if_ready = TEXT_OFFSET + 0x10220;
hv_console_write = TEXT_OFFSET + 0x10240;
hv_downcall_dispatch = TEXT_OFFSET + 0x10260;
hv_inquire_topology = TEXT_OFFSET + 0x10280;
hv_fs_findfile = TEXT_OFFSET + 0x102a0;
hv_fs_fstat = TEXT_OFFSET + 0x102c0;
hv_fs_pread = TEXT_OFFSET + 0x102e0;
hv_physaddr_read64 = TEXT_OFFSET + 0x10300;
hv_physaddr_write64 = TEXT_OFFSET + 0x10320;
hv_get_command_line = TEXT_OFFSET + 0x10340;
hv_set_caching = TEXT_OFFSET + 0x10360;
hv_bzero_page = TEXT_OFFSET + 0x10380;
hv_register_message_state = TEXT_OFFSET + 0x103a0;
hv_send_message = TEXT_OFFSET + 0x103c0;
hv_receive_message = TEXT_OFFSET + 0x103e0;
hv_inquire_context = TEXT_OFFSET + 0x10400;
hv_start_all_tiles = TEXT_OFFSET + 0x10420;
hv_dev_open = TEXT_OFFSET + 0x10440;
hv_dev_close = TEXT_OFFSET + 0x10460;
hv_dev_pread = TEXT_OFFSET + 0x10480;
hv_dev_pwrite = TEXT_OFFSET + 0x104a0;
hv_dev_poll = TEXT_OFFSET + 0x104c0;
hv_dev_poll_cancel = TEXT_OFFSET + 0x104e0;
hv_dev_preada = TEXT_OFFSET + 0x10500;
hv_dev_pwritea = TEXT_OFFSET + 0x10520;
hv_flush_remote = TEXT_OFFSET + 0x10540;
hv_console_putc = TEXT_OFFSET + 0x10560;
hv_inquire_tiles = TEXT_OFFSET + 0x10580;
hv_confstr = TEXT_OFFSET + 0x105a0;
hv_reexec = TEXT_OFFSET + 0x105c0;
hv_set_command_line = TEXT_OFFSET + 0x105e0;
hv_clear_intr = TEXT_OFFSET + 0x10600;
hv_enable_intr = TEXT_OFFSET + 0x10620;
hv_disable_intr = TEXT_OFFSET + 0x10640;
hv_raise_intr = TEXT_OFFSET + 0x10660;
hv_trigger_ipi = TEXT_OFFSET + 0x10680;
hv_store_mapping = TEXT_OFFSET + 0x106a0;
hv_inquire_realpa = TEXT_OFFSET + 0x106c0;
hv_flush_all = TEXT_OFFSET + 0x106e0;
hv_get_ipi_pte = TEXT_OFFSET + 0x10700;
hv_set_pte_super_shift = TEXT_OFFSET + 0x10720;
hv_glue_internals = TEXT_OFFSET + 0x10740;

1953
arch/tile/kernel/intvec_32.S Normal file

File diff suppressed because it is too large Load Diff

1315
arch/tile/kernel/intvec_64.S Normal file

File diff suppressed because it is too large Load Diff

295
arch/tile/kernel/irq.c Normal file
View File

@@ -0,0 +1,295 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel_stat.h>
#include <linux/uaccess.h>
#include <hv/drv_pcie_rc_intf.h>
#include <arch/spr_def.h>
#include <asm/traps.h>
/* Bit-flag stored in irq_desc->chip_data to indicate HW-cleared irqs. */
#define IS_HW_CLEARED 1
/*
* The set of interrupts we enable for arch_local_irq_enable().
* This is initialized to have just a single interrupt that the kernel
* doesn't actually use as a sentinel. During kernel init,
* interrupts are added as the kernel gets prepared to support them.
* NOTE: we could probably initialize them all statically up front.
*/
DEFINE_PER_CPU(unsigned long long, interrupts_enabled_mask) =
INITIAL_INTERRUPTS_ENABLED;
EXPORT_PER_CPU_SYMBOL(interrupts_enabled_mask);
/* Define per-tile device interrupt statistics state. */
DEFINE_PER_CPU(irq_cpustat_t, irq_stat) ____cacheline_internodealigned_in_smp;
EXPORT_PER_CPU_SYMBOL(irq_stat);
/*
* Define per-tile irq disable mask; the hardware/HV only has a single
* mask that we use to implement both masking and disabling.
*/
static DEFINE_PER_CPU(unsigned long, irq_disable_mask)
____cacheline_internodealigned_in_smp;
/*
* Per-tile IRQ nesting depth. Used to make sure we enable newly
* enabled IRQs before exiting the outermost interrupt.
*/
static DEFINE_PER_CPU(int, irq_depth);
/* State for allocating IRQs on Gx. */
#if CHIP_HAS_IPI()
static unsigned long available_irqs = ~(1UL << IRQ_RESCHEDULE);
static DEFINE_SPINLOCK(available_irqs_lock);
#endif
#if CHIP_HAS_IPI()
/* Use SPRs to manipulate device interrupts. */
#define mask_irqs(irq_mask) __insn_mtspr(SPR_IPI_MASK_SET_K, irq_mask)
#define unmask_irqs(irq_mask) __insn_mtspr(SPR_IPI_MASK_RESET_K, irq_mask)
#define clear_irqs(irq_mask) __insn_mtspr(SPR_IPI_EVENT_RESET_K, irq_mask)
#else
/* Use HV to manipulate device interrupts. */
#define mask_irqs(irq_mask) hv_disable_intr(irq_mask)
#define unmask_irqs(irq_mask) hv_enable_intr(irq_mask)
#define clear_irqs(irq_mask) hv_clear_intr(irq_mask)
#endif
/*
* The interrupt handling path, implemented in terms of HV interrupt
* emulation on TILE64 and TILEPro, and IPI hardware on TILE-Gx.
*/
void tile_dev_intr(struct pt_regs *regs, int intnum)
{
int depth = __get_cpu_var(irq_depth)++;
unsigned long original_irqs;
unsigned long remaining_irqs;
struct pt_regs *old_regs;
#if CHIP_HAS_IPI()
/*
* Pending interrupts are listed in an SPR. We might be
* nested, so be sure to only handle irqs that weren't already
* masked by a previous interrupt. Then, mask out the ones
* we're going to handle.
*/
unsigned long masked = __insn_mfspr(SPR_IPI_MASK_K);
original_irqs = __insn_mfspr(SPR_IPI_EVENT_K) & ~masked;
__insn_mtspr(SPR_IPI_MASK_SET_K, original_irqs);
#else
/*
* Hypervisor performs the equivalent of the Gx code above and
* then puts the pending interrupt mask into a system save reg
* for us to find.
*/
original_irqs = __insn_mfspr(SPR_SYSTEM_SAVE_K_3);
#endif
remaining_irqs = original_irqs;
/* Track time spent here in an interrupt context. */
old_regs = set_irq_regs(regs);
irq_enter();
#ifdef CONFIG_DEBUG_STACKOVERFLOW
/* Debugging check for stack overflow: less than 1/8th stack free? */
{
long sp = stack_pointer - (long) current_thread_info();
if (unlikely(sp < (sizeof(struct thread_info) + STACK_WARN))) {
pr_emerg("tile_dev_intr: "
"stack overflow: %ld\n",
sp - sizeof(struct thread_info));
dump_stack();
}
}
#endif
while (remaining_irqs) {
unsigned long irq = __ffs(remaining_irqs);
remaining_irqs &= ~(1UL << irq);
/* Count device irqs; Linux IPIs are counted elsewhere. */
if (irq != IRQ_RESCHEDULE)
__get_cpu_var(irq_stat).irq_dev_intr_count++;
generic_handle_irq(irq);
}
/*
* If we weren't nested, turn on all enabled interrupts,
* including any that were reenabled during interrupt
* handling.
*/
if (depth == 0)
unmask_irqs(~__get_cpu_var(irq_disable_mask));
__get_cpu_var(irq_depth)--;
/*
* Track time spent against the current process again and
* process any softirqs if they are waiting.
*/
irq_exit();
set_irq_regs(old_regs);
}
/*
* Remove an irq from the disabled mask. If we're in an interrupt
* context, defer enabling the HW interrupt until we leave.
*/
static void tile_irq_chip_enable(struct irq_data *d)
{
get_cpu_var(irq_disable_mask) &= ~(1UL << d->irq);
if (__get_cpu_var(irq_depth) == 0)
unmask_irqs(1UL << d->irq);
put_cpu_var(irq_disable_mask);
}
/*
* Add an irq to the disabled mask. We disable the HW interrupt
* immediately so that there's no possibility of it firing. If we're
* in an interrupt context, the return path is careful to avoid
* unmasking a newly disabled interrupt.
*/
static void tile_irq_chip_disable(struct irq_data *d)
{
get_cpu_var(irq_disable_mask) |= (1UL << d->irq);
mask_irqs(1UL << d->irq);
put_cpu_var(irq_disable_mask);
}
/* Mask an interrupt. */
static void tile_irq_chip_mask(struct irq_data *d)
{
mask_irqs(1UL << d->irq);
}
/* Unmask an interrupt. */
static void tile_irq_chip_unmask(struct irq_data *d)
{
unmask_irqs(1UL << d->irq);
}
/*
* Clear an interrupt before processing it so that any new assertions
* will trigger another irq.
*/
static void tile_irq_chip_ack(struct irq_data *d)
{
if ((unsigned long)irq_data_get_irq_chip_data(d) != IS_HW_CLEARED)
clear_irqs(1UL << d->irq);
}
/*
* For per-cpu interrupts, we need to avoid unmasking any interrupts
* that we disabled via disable_percpu_irq().
*/
static void tile_irq_chip_eoi(struct irq_data *d)
{
if (!(__get_cpu_var(irq_disable_mask) & (1UL << d->irq)))
unmask_irqs(1UL << d->irq);
}
static struct irq_chip tile_irq_chip = {
.name = "tile_irq_chip",
.irq_enable = tile_irq_chip_enable,
.irq_disable = tile_irq_chip_disable,
.irq_ack = tile_irq_chip_ack,
.irq_eoi = tile_irq_chip_eoi,
.irq_mask = tile_irq_chip_mask,
.irq_unmask = tile_irq_chip_unmask,
};
void __init init_IRQ(void)
{
ipi_init();
}
void __cpuinit setup_irq_regs(void)
{
/* Enable interrupt delivery. */
unmask_irqs(~0UL);
#if CHIP_HAS_IPI()
arch_local_irq_unmask(INT_IPI_K);
#endif
}
void tile_irq_activate(unsigned int irq, int tile_irq_type)
{
/*
* We use handle_level_irq() by default because the pending
* interrupt vector (whether modeled by the HV on TILE64 and
* TILEPro or implemented in hardware on TILE-Gx) has
* level-style semantics for each bit. An interrupt fires
* whenever a bit is high, not just at edges.
*/
irq_flow_handler_t handle = handle_level_irq;
if (tile_irq_type == TILE_IRQ_PERCPU)
handle = handle_percpu_irq;
irq_set_chip_and_handler(irq, &tile_irq_chip, handle);
/*
* Flag interrupts that are hardware-cleared so that ack()
* won't clear them.
*/
if (tile_irq_type == TILE_IRQ_HW_CLEAR)
irq_set_chip_data(irq, (void *)IS_HW_CLEARED);
}
EXPORT_SYMBOL(tile_irq_activate);
void ack_bad_irq(unsigned int irq)
{
pr_err("unexpected IRQ trap at vector %02x\n", irq);
}
/*
* Generic, controller-independent functions:
*/
#if CHIP_HAS_IPI()
int create_irq(void)
{
unsigned long flags;
int result;
spin_lock_irqsave(&available_irqs_lock, flags);
if (available_irqs == 0)
result = -ENOMEM;
else {
result = __ffs(available_irqs);
available_irqs &= ~(1UL << result);
dynamic_irq_init(result);
}
spin_unlock_irqrestore(&available_irqs_lock, flags);
return result;
}
EXPORT_SYMBOL(create_irq);
void destroy_irq(unsigned int irq)
{
unsigned long flags;
spin_lock_irqsave(&available_irqs_lock, flags);
available_irqs |= (1UL << irq);
dynamic_irq_cleanup(irq);
spin_unlock_irqrestore(&available_irqs_lock, flags);
}
EXPORT_SYMBOL(destroy_irq);
#endif

View File

@@ -0,0 +1,304 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* based on machine_kexec.c from other architectures in linux-2.6.18
*/
#include <linux/mm.h>
#include <linux/kexec.h>
#include <linux/delay.h>
#include <linux/reboot.h>
#include <linux/errno.h>
#include <linux/vmalloc.h>
#include <linux/cpumask.h>
#include <linux/kernel.h>
#include <linux/elf.h>
#include <linux/highmem.h>
#include <linux/mmu_context.h>
#include <linux/io.h>
#include <linux/timex.h>
#include <asm/pgtable.h>
#include <asm/pgalloc.h>
#include <asm/cacheflush.h>
#include <asm/checksum.h>
#include <asm/tlbflush.h>
#include <asm/homecache.h>
#include <hv/hypervisor.h>
/*
* This stuff is not in elf.h and is not in any other kernel include.
* This stuff is needed below in the little boot notes parser to
* extract the command line so we can pass it to the hypervisor.
*/
struct Elf32_Bhdr {
Elf32_Word b_signature;
Elf32_Word b_size;
Elf32_Half b_checksum;
Elf32_Half b_records;
};
#define ELF_BOOT_MAGIC 0x0E1FB007
#define EBN_COMMAND_LINE 0x00000004
#define roundupsz(X) (((X) + 3) & ~3)
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void machine_shutdown(void)
{
/*
* Normally we would stop all the other processors here, but
* the check in machine_kexec_prepare below ensures we'll only
* get this far if we've been booted with "nosmp" on the
* command line or without CONFIG_SMP so there's nothing to do
* here (for now).
*/
}
void machine_crash_shutdown(struct pt_regs *regs)
{
/*
* Cannot happen. This type of kexec is disabled on this
* architecture (and enforced in machine_kexec_prepare below).
*/
}
int machine_kexec_prepare(struct kimage *image)
{
if (num_online_cpus() > 1) {
pr_warning("%s: detected attempt to kexec "
"with num_online_cpus() > 1\n",
__func__);
return -ENOSYS;
}
if (image->type != KEXEC_TYPE_DEFAULT) {
pr_warning("%s: detected attempt to kexec "
"with unsupported type: %d\n",
__func__,
image->type);
return -ENOSYS;
}
return 0;
}
void machine_kexec_cleanup(struct kimage *image)
{
/*
* We did nothing in machine_kexec_prepare,
* so we have nothing to do here.
*/
}
/*
* If we can find elf boot notes on this page, return the command
* line. Otherwise, silently return null. Somewhat kludgy, but no
* good way to do this without significantly rearchitecting the
* architecture-independent kexec code.
*/
static unsigned char *kexec_bn2cl(void *pg)
{
struct Elf32_Bhdr *bhdrp;
Elf32_Nhdr *nhdrp;
unsigned char *desc;
unsigned char *command_line;
__sum16 csum;
bhdrp = (struct Elf32_Bhdr *) pg;
/*
* This routine is invoked for every source page, so make
* sure to quietly ignore every impossible page.
*/
if (bhdrp->b_signature != ELF_BOOT_MAGIC ||
bhdrp->b_size > PAGE_SIZE)
return 0;
/*
* If we get a checksum mismatch, warn with the checksum
* so we can diagnose better.
*/
csum = ip_compute_csum(pg, bhdrp->b_size);
if (csum != 0) {
pr_warning("%s: bad checksum %#x (size %d)\n",
__func__, csum, bhdrp->b_size);
return 0;
}
nhdrp = (Elf32_Nhdr *) (bhdrp + 1);
while (nhdrp->n_type != EBN_COMMAND_LINE) {
desc = (unsigned char *) (nhdrp + 1);
desc += roundupsz(nhdrp->n_descsz);
nhdrp = (Elf32_Nhdr *) desc;
/* still in bounds? */
if ((unsigned char *) (nhdrp + 1) >
((unsigned char *) pg) + bhdrp->b_size) {
pr_info("%s: out of bounds\n", __func__);
return 0;
}
}
command_line = (unsigned char *) (nhdrp + 1);
desc = command_line;
while (*desc != '\0') {
desc++;
if (((unsigned long)desc & PAGE_MASK) != (unsigned long)pg) {
pr_info("%s: ran off end of page\n",
__func__);
return 0;
}
}
return command_line;
}
static void kexec_find_and_set_command_line(struct kimage *image)
{
kimage_entry_t *ptr, entry;
unsigned char *command_line = 0;
unsigned char *r;
HV_Errno hverr;
for (ptr = &image->head;
(entry = *ptr) && !(entry & IND_DONE);
ptr = (entry & IND_INDIRECTION) ?
phys_to_virt((entry & PAGE_MASK)) : ptr + 1) {
if ((entry & IND_SOURCE)) {
void *va =
kmap_atomic_pfn(entry >> PAGE_SHIFT);
r = kexec_bn2cl(va);
if (r) {
command_line = r;
break;
}
kunmap_atomic(va);
}
}
if (command_line != 0) {
pr_info("setting new command line to \"%s\"\n",
command_line);
hverr = hv_set_command_line(
(HV_VirtAddr) command_line, strlen(command_line));
kunmap_atomic(command_line);
} else {
pr_info("%s: no command line found; making empty\n",
__func__);
hverr = hv_set_command_line((HV_VirtAddr) command_line, 0);
}
if (hverr)
pr_warning("%s: hv_set_command_line returned error: %d\n",
__func__, hverr);
}
/*
* The kexec code range-checks all its PAs, so to avoid having it run
* amok and allocate memory and then sequester it from every other
* controller, we force it to come from controller zero. We also
* disable the oom-killer since if we do end up running out of memory,
* that almost certainly won't help.
*/
struct page *kimage_alloc_pages_arch(gfp_t gfp_mask, unsigned int order)
{
gfp_mask |= __GFP_THISNODE | __GFP_NORETRY;
return alloc_pages_node(0, gfp_mask, order);
}
/*
* Address range in which pa=va mapping is set in setup_quasi_va_is_pa().
* For tilepro, PAGE_OFFSET is used since this is the largest possbile value
* for tilepro, while for tilegx, we limit it to entire middle level page
* table which we assume has been allocated and is undoubtedly large enough.
*/
#ifndef __tilegx__
#define QUASI_VA_IS_PA_ADDR_RANGE PAGE_OFFSET
#else
#define QUASI_VA_IS_PA_ADDR_RANGE PGDIR_SIZE
#endif
static void setup_quasi_va_is_pa(void)
{
HV_PTE pte;
unsigned long i;
/*
* Flush our TLB to prevent conflicts between the previous contents
* and the new stuff we're about to add.
*/
local_flush_tlb_all();
/*
* setup VA is PA, at least up to QUASI_VA_IS_PA_ADDR_RANGE.
* Note here we assume that level-1 page table is defined by
* HPAGE_SIZE.
*/
pte = hv_pte(_PAGE_KERNEL | _PAGE_HUGE_PAGE);
pte = hv_pte_set_mode(pte, HV_PTE_MODE_CACHE_NO_L3);
for (i = 0; i < (QUASI_VA_IS_PA_ADDR_RANGE >> HPAGE_SHIFT); i++) {
unsigned long vaddr = i << HPAGE_SHIFT;
pgd_t *pgd = pgd_offset(current->mm, vaddr);
pud_t *pud = pud_offset(pgd, vaddr);
pte_t *ptep = (pte_t *) pmd_offset(pud, vaddr);
unsigned long pfn = i << (HPAGE_SHIFT - PAGE_SHIFT);
if (pfn_valid(pfn))
__set_pte(ptep, pfn_pte(pfn, pte));
}
}
void machine_kexec(struct kimage *image)
{
void *reboot_code_buffer;
pte_t *ptep;
void (*rnk)(unsigned long, void *, unsigned long)
__noreturn;
/* Mask all interrupts before starting to reboot. */
interrupt_mask_set_mask(~0ULL);
kexec_find_and_set_command_line(image);
/*
* Adjust the home caching of the control page to be cached on
* this cpu, and copy the assembly helper into the control
* code page, which we map in the vmalloc area.
*/
homecache_change_page_home(image->control_code_page, 0,
smp_processor_id());
reboot_code_buffer = page_address(image->control_code_page);
BUG_ON(reboot_code_buffer == NULL);
ptep = virt_to_pte(NULL, (unsigned long)reboot_code_buffer);
__set_pte(ptep, pte_mkexec(*ptep));
memcpy(reboot_code_buffer, relocate_new_kernel,
relocate_new_kernel_size);
__flush_icache_range(
(unsigned long) reboot_code_buffer,
(unsigned long) reboot_code_buffer + relocate_new_kernel_size);
setup_quasi_va_is_pa();
/* now call it */
rnk = reboot_code_buffer;
(*rnk)(image->head, reboot_code_buffer, image->start);
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/hardirq.h>
#include <linux/ptrace.h>
#include <asm/hv_driver.h>
#include <asm/irq_regs.h>
#include <asm/traps.h>
#include <hv/hypervisor.h>
#include <arch/interrupts.h>
/* All messages are stored here */
static DEFINE_PER_CPU(HV_MsgState, msg_state);
void __cpuinit init_messaging(void)
{
/* Allocate storage for messages in kernel space */
HV_MsgState *state = &__get_cpu_var(msg_state);
int rc = hv_register_message_state(state);
if (rc != HV_OK)
panic("hv_register_message_state: error %d", rc);
/* Make sure downcall interrupts will be enabled. */
arch_local_irq_unmask(INT_INTCTRL_K);
}
void hv_message_intr(struct pt_regs *regs, int intnum)
{
/*
* We enter with interrupts disabled and leave them disabled,
* to match expectations of called functions (e.g.
* do_ccupdate_local() in mm/slab.c). This is also consistent
* with normal call entry for device interrupts.
*/
int message[HV_MAX_MESSAGE_SIZE/sizeof(int)];
HV_RcvMsgInfo rmi;
int nmsgs = 0;
/* Track time spent here in an interrupt context */
struct pt_regs *old_regs = set_irq_regs(regs);
irq_enter();
#ifdef CONFIG_DEBUG_STACKOVERFLOW
/* Debugging check for stack overflow: less than 1/8th stack free? */
{
long sp = stack_pointer - (long) current_thread_info();
if (unlikely(sp < (sizeof(struct thread_info) + STACK_WARN))) {
pr_emerg("hv_message_intr: "
"stack overflow: %ld\n",
sp - sizeof(struct thread_info));
dump_stack();
}
}
#endif
while (1) {
rmi = hv_receive_message(__get_cpu_var(msg_state),
(HV_VirtAddr) message,
sizeof(message));
if (rmi.msglen == 0)
break;
if (rmi.msglen < 0)
panic("hv_receive_message failed: %d", rmi.msglen);
++nmsgs;
if (rmi.source == HV_MSG_TILE) {
int tag;
/* we just send tags for now */
BUG_ON(rmi.msglen != sizeof(int));
tag = message[0];
#ifdef CONFIG_SMP
evaluate_message(message[0]);
#else
panic("Received IPI message %d in UP mode", tag);
#endif
} else if (rmi.source == HV_MSG_INTR) {
HV_IntrMsg *him = (HV_IntrMsg *)message;
struct hv_driver_cb *cb =
(struct hv_driver_cb *)him->intarg;
cb->callback(cb, him->intdata);
__get_cpu_var(irq_stat).irq_hv_msg_count++;
}
}
/*
* We shouldn't have gotten a message downcall with no
* messages available.
*/
if (nmsgs == 0)
panic("Message downcall invoked with no messages!");
/*
* Track time spent against the current process again and
* process any softirqs if they are waiting.
*/
irq_exit();
set_irq_regs(old_regs);
}

232
arch/tile/kernel/module.c Normal file
View File

@@ -0,0 +1,232 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* Based on i386 version, copyright (C) 2001 Rusty Russell.
*/
#include <linux/moduleloader.h>
#include <linux/elf.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <asm/pgtable.h>
#include <asm/homecache.h>
#include <arch/opcode.h>
#ifdef MODULE_DEBUG
#define DEBUGP printk
#else
#define DEBUGP(fmt...)
#endif
/*
* Allocate some address space in the range MEM_MODULE_START to
* MEM_MODULE_END and populate it with memory.
*/
void *module_alloc(unsigned long size)
{
struct page **pages;
pgprot_t prot_rwx = __pgprot(_PAGE_KERNEL | _PAGE_KERNEL_EXEC);
struct vm_struct *area;
int i = 0;
int npages;
npages = (size + PAGE_SIZE - 1) / PAGE_SIZE;
pages = kmalloc(npages * sizeof(struct page *), GFP_KERNEL);
if (pages == NULL)
return NULL;
for (; i < npages; ++i) {
pages[i] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
if (!pages[i])
goto error;
}
area = __get_vm_area(size, VM_ALLOC, MEM_MODULE_START, MEM_MODULE_END);
if (!area)
goto error;
area->nr_pages = npages;
area->pages = pages;
if (map_vm_area(area, prot_rwx, &pages)) {
vunmap(area->addr);
goto error;
}
return area->addr;
error:
while (--i >= 0)
__free_page(pages[i]);
kfree(pages);
return NULL;
}
/* Free memory returned from module_alloc */
void module_free(struct module *mod, void *module_region)
{
vfree(module_region);
/* Globally flush the L1 icache. */
flush_remote(0, HV_FLUSH_EVICT_L1I, cpu_online_mask,
0, 0, 0, NULL, NULL, 0);
/*
* FIXME: If module_region == mod->module_init, trim exception
* table entries.
*/
}
#ifdef __tilegx__
/*
* Validate that the high 16 bits of "value" is just the sign-extension of
* the low 48 bits.
*/
static int validate_hw2_last(long value, struct module *me)
{
if (((value << 16) >> 16) != value) {
pr_warning("module %s: Out of range HW2_LAST value %#lx\n",
me->name, value);
return 0;
}
return 1;
}
/*
* Validate that "value" isn't too big to hold in a JumpOff relocation.
*/
static int validate_jumpoff(long value)
{
/* Determine size of jump offset. */
int shift = __builtin_clzl(get_JumpOff_X1(create_JumpOff_X1(-1)));
/* Check to see if it fits into the relocation slot. */
long f = get_JumpOff_X1(create_JumpOff_X1(value));
f = (f << shift) >> shift;
return f == value;
}
#endif
int apply_relocate_add(Elf_Shdr *sechdrs,
const char *strtab,
unsigned int symindex,
unsigned int relsec,
struct module *me)
{
unsigned int i;
Elf_Rela *rel = (void *)sechdrs[relsec].sh_addr;
Elf_Sym *sym;
u64 *location;
unsigned long value;
DEBUGP("Applying relocate section %u to %u\n", relsec,
sechdrs[relsec].sh_info);
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;
/*
* This is the symbol it is referring to.
* Note that all undefined symbols have been resolved.
*/
sym = (Elf_Sym *)sechdrs[symindex].sh_addr
+ ELF_R_SYM(rel[i].r_info);
value = sym->st_value + rel[i].r_addend;
switch (ELF_R_TYPE(rel[i].r_info)) {
#ifdef __LITTLE_ENDIAN
# define MUNGE(func) \
(*location = ((*location & ~func(-1)) | func(value)))
#else
/*
* Instructions are always little-endian, so when we read them as data,
* we have to swap them around before and after modifying them.
*/
# define MUNGE(func) \
(*location = swab64((swab64(*location) & ~func(-1)) | func(value)))
#endif
#ifndef __tilegx__
case R_TILE_32:
*(uint32_t *)location = value;
break;
case R_TILE_IMM16_X0_HA:
value = (value + 0x8000) >> 16;
/*FALLTHROUGH*/
case R_TILE_IMM16_X0_LO:
MUNGE(create_Imm16_X0);
break;
case R_TILE_IMM16_X1_HA:
value = (value + 0x8000) >> 16;
/*FALLTHROUGH*/
case R_TILE_IMM16_X1_LO:
MUNGE(create_Imm16_X1);
break;
case R_TILE_JOFFLONG_X1:
value -= (unsigned long) location; /* pc-relative */
value = (long) value >> 3; /* count by instrs */
MUNGE(create_JOffLong_X1);
break;
#else
case R_TILEGX_64:
*location = value;
break;
case R_TILEGX_IMM16_X0_HW2_LAST:
if (!validate_hw2_last(value, me))
return -ENOEXEC;
value >>= 16;
/*FALLTHROUGH*/
case R_TILEGX_IMM16_X0_HW1:
value >>= 16;
/*FALLTHROUGH*/
case R_TILEGX_IMM16_X0_HW0:
MUNGE(create_Imm16_X0);
break;
case R_TILEGX_IMM16_X1_HW2_LAST:
if (!validate_hw2_last(value, me))
return -ENOEXEC;
value >>= 16;
/*FALLTHROUGH*/
case R_TILEGX_IMM16_X1_HW1:
value >>= 16;
/*FALLTHROUGH*/
case R_TILEGX_IMM16_X1_HW0:
MUNGE(create_Imm16_X1);
break;
case R_TILEGX_JUMPOFF_X1:
value -= (unsigned long) location; /* pc-relative */
value = (long) value >> 3; /* count by instrs */
if (!validate_jumpoff(value)) {
pr_warning("module %s: Out of range jump to"
" %#llx at %#llx (%p)\n", me->name,
sym->st_value + rel[i].r_addend,
rel[i].r_offset, location);
return -ENOEXEC;
}
MUNGE(create_JumpOff_X1);
break;
#endif
#undef MUNGE
default:
pr_err("module %s: Unknown relocation: %d\n",
me->name, (int) ELF_R_TYPE(rel[i].r_info));
return -ENOEXEC;
}
}
return 0;
}

586
arch/tile/kernel/pci-dma.c Normal file
View File

@@ -0,0 +1,586 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/swiotlb.h>
#include <linux/vmalloc.h>
#include <linux/export.h>
#include <asm/tlbflush.h>
#include <asm/homecache.h>
/* Generic DMA mapping functions: */
/*
* Allocate what Linux calls "coherent" memory. On TILEPro this is
* uncached memory; on TILE-Gx it is hash-for-home memory.
*/
#ifdef __tilepro__
#define PAGE_HOME_DMA PAGE_HOME_UNCACHED
#else
#define PAGE_HOME_DMA PAGE_HOME_HASH
#endif
static void *tile_dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
struct dma_attrs *attrs)
{
u64 dma_mask = dev->coherent_dma_mask ?: DMA_BIT_MASK(32);
int node = dev_to_node(dev);
int order = get_order(size);
struct page *pg;
dma_addr_t addr;
gfp |= __GFP_ZERO;
/*
* If the mask specifies that the memory be in the first 4 GB, then
* we force the allocation to come from the DMA zone. We also
* force the node to 0 since that's the only node where the DMA
* zone isn't empty. If the mask size is smaller than 32 bits, we
* may still not be able to guarantee a suitable memory address, in
* which case we will return NULL. But such devices are uncommon.
*/
if (dma_mask <= DMA_BIT_MASK(32)) {
gfp |= GFP_DMA;
node = 0;
}
pg = homecache_alloc_pages_node(node, gfp, order, PAGE_HOME_DMA);
if (pg == NULL)
return NULL;
addr = page_to_phys(pg);
if (addr + size > dma_mask) {
__homecache_free_pages(pg, order);
return NULL;
}
*dma_handle = addr;
return page_address(pg);
}
/*
* Free memory that was allocated with tile_dma_alloc_coherent.
*/
static void tile_dma_free_coherent(struct device *dev, size_t size,
void *vaddr, dma_addr_t dma_handle,
struct dma_attrs *attrs)
{
homecache_free_pages((unsigned long)vaddr, get_order(size));
}
/*
* The map routines "map" the specified address range for DMA
* accesses. The memory belongs to the device after this call is
* issued, until it is unmapped with dma_unmap_single.
*
* We don't need to do any mapping, we just flush the address range
* out of the cache and return a DMA address.
*
* The unmap routines do whatever is necessary before the processor
* accesses the memory again, and must be called before the driver
* touches the memory. We can get away with a cache invalidate if we
* can count on nothing having been touched.
*/
/* Set up a single page for DMA access. */
static void __dma_prep_page(struct page *page, unsigned long offset,
size_t size, enum dma_data_direction direction)
{
/*
* Flush the page from cache if necessary.
* On tilegx, data is delivered to hash-for-home L3; on tilepro,
* data is delivered direct to memory.
*
* NOTE: If we were just doing DMA_TO_DEVICE we could optimize
* this to be a "flush" not a "finv" and keep some of the
* state in cache across the DMA operation, but it doesn't seem
* worth creating the necessary flush_buffer_xxx() infrastructure.
*/
int home = page_home(page);
switch (home) {
case PAGE_HOME_HASH:
#ifdef __tilegx__
return;
#endif
break;
case PAGE_HOME_UNCACHED:
#ifdef __tilepro__
return;
#endif
break;
case PAGE_HOME_IMMUTABLE:
/* Should be going to the device only. */
BUG_ON(direction == DMA_FROM_DEVICE ||
direction == DMA_BIDIRECTIONAL);
return;
case PAGE_HOME_INCOHERENT:
/* Incoherent anyway, so no need to work hard here. */
return;
default:
BUG_ON(home < 0 || home >= NR_CPUS);
break;
}
homecache_finv_page(page);
#ifdef DEBUG_ALIGNMENT
/* Warn if the region isn't cacheline aligned. */
if (offset & (L2_CACHE_BYTES - 1) || (size & (L2_CACHE_BYTES - 1)))
pr_warn("Unaligned DMA to non-hfh memory: PA %#llx/%#lx\n",
PFN_PHYS(page_to_pfn(page)) + offset, size);
#endif
}
/* Make the page ready to be read by the core. */
static void __dma_complete_page(struct page *page, unsigned long offset,
size_t size, enum dma_data_direction direction)
{
#ifdef __tilegx__
switch (page_home(page)) {
case PAGE_HOME_HASH:
/* I/O device delivered data the way the cpu wanted it. */
break;
case PAGE_HOME_INCOHERENT:
/* Incoherent anyway, so no need to work hard here. */
break;
case PAGE_HOME_IMMUTABLE:
/* Extra read-only copies are not a problem. */
break;
default:
/* Flush the bogus hash-for-home I/O entries to memory. */
homecache_finv_map_page(page, PAGE_HOME_HASH);
break;
}
#endif
}
static void __dma_prep_pa_range(dma_addr_t dma_addr, size_t size,
enum dma_data_direction direction)
{
struct page *page = pfn_to_page(PFN_DOWN(dma_addr));
unsigned long offset = dma_addr & (PAGE_SIZE - 1);
size_t bytes = min(size, (size_t)(PAGE_SIZE - offset));
while (size != 0) {
__dma_prep_page(page, offset, bytes, direction);
size -= bytes;
++page;
offset = 0;
bytes = min((size_t)PAGE_SIZE, size);
}
}
static void __dma_complete_pa_range(dma_addr_t dma_addr, size_t size,
enum dma_data_direction direction)
{
struct page *page = pfn_to_page(PFN_DOWN(dma_addr));
unsigned long offset = dma_addr & (PAGE_SIZE - 1);
size_t bytes = min(size, (size_t)(PAGE_SIZE - offset));
while (size != 0) {
__dma_complete_page(page, offset, bytes, direction);
size -= bytes;
++page;
offset = 0;
bytes = min((size_t)PAGE_SIZE, size);
}
}
static int tile_dma_map_sg(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction direction,
struct dma_attrs *attrs)
{
struct scatterlist *sg;
int i;
BUG_ON(!valid_dma_direction(direction));
WARN_ON(nents == 0 || sglist->length == 0);
for_each_sg(sglist, sg, nents, i) {
sg->dma_address = sg_phys(sg);
__dma_prep_pa_range(sg->dma_address, sg->length, direction);
#ifdef CONFIG_NEED_SG_DMA_LENGTH
sg->dma_length = sg->length;
#endif
}
return nents;
}
static void tile_dma_unmap_sg(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction direction,
struct dma_attrs *attrs)
{
struct scatterlist *sg;
int i;
BUG_ON(!valid_dma_direction(direction));
for_each_sg(sglist, sg, nents, i) {
sg->dma_address = sg_phys(sg);
__dma_complete_pa_range(sg->dma_address, sg->length,
direction);
}
}
static dma_addr_t tile_dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction direction,
struct dma_attrs *attrs)
{
BUG_ON(!valid_dma_direction(direction));
BUG_ON(offset + size > PAGE_SIZE);
__dma_prep_page(page, offset, size, direction);
return page_to_pa(page) + offset;
}
static void tile_dma_unmap_page(struct device *dev, dma_addr_t dma_address,
size_t size, enum dma_data_direction direction,
struct dma_attrs *attrs)
{
BUG_ON(!valid_dma_direction(direction));
__dma_complete_page(pfn_to_page(PFN_DOWN(dma_address)),
dma_address & PAGE_OFFSET, size, direction);
}
static void tile_dma_sync_single_for_cpu(struct device *dev,
dma_addr_t dma_handle,
size_t size,
enum dma_data_direction direction)
{
BUG_ON(!valid_dma_direction(direction));
__dma_complete_pa_range(dma_handle, size, direction);
}
static void tile_dma_sync_single_for_device(struct device *dev,
dma_addr_t dma_handle, size_t size,
enum dma_data_direction direction)
{
__dma_prep_pa_range(dma_handle, size, direction);
}
static void tile_dma_sync_sg_for_cpu(struct device *dev,
struct scatterlist *sglist, int nelems,
enum dma_data_direction direction)
{
struct scatterlist *sg;
int i;
BUG_ON(!valid_dma_direction(direction));
WARN_ON(nelems == 0 || sglist->length == 0);
for_each_sg(sglist, sg, nelems, i) {
dma_sync_single_for_cpu(dev, sg->dma_address,
sg_dma_len(sg), direction);
}
}
static void tile_dma_sync_sg_for_device(struct device *dev,
struct scatterlist *sglist, int nelems,
enum dma_data_direction direction)
{
struct scatterlist *sg;
int i;
BUG_ON(!valid_dma_direction(direction));
WARN_ON(nelems == 0 || sglist->length == 0);
for_each_sg(sglist, sg, nelems, i) {
dma_sync_single_for_device(dev, sg->dma_address,
sg_dma_len(sg), direction);
}
}
static inline int
tile_dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
{
return 0;
}
static inline int
tile_dma_supported(struct device *dev, u64 mask)
{
return 1;
}
static struct dma_map_ops tile_default_dma_map_ops = {
.alloc = tile_dma_alloc_coherent,
.free = tile_dma_free_coherent,
.map_page = tile_dma_map_page,
.unmap_page = tile_dma_unmap_page,
.map_sg = tile_dma_map_sg,
.unmap_sg = tile_dma_unmap_sg,
.sync_single_for_cpu = tile_dma_sync_single_for_cpu,
.sync_single_for_device = tile_dma_sync_single_for_device,
.sync_sg_for_cpu = tile_dma_sync_sg_for_cpu,
.sync_sg_for_device = tile_dma_sync_sg_for_device,
.mapping_error = tile_dma_mapping_error,
.dma_supported = tile_dma_supported
};
struct dma_map_ops *tile_dma_map_ops = &tile_default_dma_map_ops;
EXPORT_SYMBOL(tile_dma_map_ops);
/* Generic PCI DMA mapping functions */
static void *tile_pci_dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
struct dma_attrs *attrs)
{
int node = dev_to_node(dev);
int order = get_order(size);
struct page *pg;
dma_addr_t addr;
gfp |= __GFP_ZERO;
pg = homecache_alloc_pages_node(node, gfp, order, PAGE_HOME_DMA);
if (pg == NULL)
return NULL;
addr = page_to_phys(pg);
*dma_handle = phys_to_dma(dev, addr);
return page_address(pg);
}
/*
* Free memory that was allocated with tile_pci_dma_alloc_coherent.
*/
static void tile_pci_dma_free_coherent(struct device *dev, size_t size,
void *vaddr, dma_addr_t dma_handle,
struct dma_attrs *attrs)
{
homecache_free_pages((unsigned long)vaddr, get_order(size));
}
static int tile_pci_dma_map_sg(struct device *dev, struct scatterlist *sglist,
int nents, enum dma_data_direction direction,
struct dma_attrs *attrs)
{
struct scatterlist *sg;
int i;
BUG_ON(!valid_dma_direction(direction));
WARN_ON(nents == 0 || sglist->length == 0);
for_each_sg(sglist, sg, nents, i) {
sg->dma_address = sg_phys(sg);
__dma_prep_pa_range(sg->dma_address, sg->length, direction);
sg->dma_address = phys_to_dma(dev, sg->dma_address);
#ifdef CONFIG_NEED_SG_DMA_LENGTH
sg->dma_length = sg->length;
#endif
}
return nents;
}
static void tile_pci_dma_unmap_sg(struct device *dev,
struct scatterlist *sglist, int nents,
enum dma_data_direction direction,
struct dma_attrs *attrs)
{
struct scatterlist *sg;
int i;
BUG_ON(!valid_dma_direction(direction));
for_each_sg(sglist, sg, nents, i) {
sg->dma_address = sg_phys(sg);
__dma_complete_pa_range(sg->dma_address, sg->length,
direction);
}
}
static dma_addr_t tile_pci_dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction direction,
struct dma_attrs *attrs)
{
BUG_ON(!valid_dma_direction(direction));
BUG_ON(offset + size > PAGE_SIZE);
__dma_prep_page(page, offset, size, direction);
return phys_to_dma(dev, page_to_pa(page) + offset);
}
static void tile_pci_dma_unmap_page(struct device *dev, dma_addr_t dma_address,
size_t size,
enum dma_data_direction direction,
struct dma_attrs *attrs)
{
BUG_ON(!valid_dma_direction(direction));
dma_address = dma_to_phys(dev, dma_address);
__dma_complete_page(pfn_to_page(PFN_DOWN(dma_address)),
dma_address & PAGE_OFFSET, size, direction);
}
static void tile_pci_dma_sync_single_for_cpu(struct device *dev,
dma_addr_t dma_handle,
size_t size,
enum dma_data_direction direction)
{
BUG_ON(!valid_dma_direction(direction));
dma_handle = dma_to_phys(dev, dma_handle);
__dma_complete_pa_range(dma_handle, size, direction);
}
static void tile_pci_dma_sync_single_for_device(struct device *dev,
dma_addr_t dma_handle,
size_t size,
enum dma_data_direction
direction)
{
dma_handle = dma_to_phys(dev, dma_handle);
__dma_prep_pa_range(dma_handle, size, direction);
}
static void tile_pci_dma_sync_sg_for_cpu(struct device *dev,
struct scatterlist *sglist,
int nelems,
enum dma_data_direction direction)
{
struct scatterlist *sg;
int i;
BUG_ON(!valid_dma_direction(direction));
WARN_ON(nelems == 0 || sglist->length == 0);
for_each_sg(sglist, sg, nelems, i) {
dma_sync_single_for_cpu(dev, sg->dma_address,
sg_dma_len(sg), direction);
}
}
static void tile_pci_dma_sync_sg_for_device(struct device *dev,
struct scatterlist *sglist,
int nelems,
enum dma_data_direction direction)
{
struct scatterlist *sg;
int i;
BUG_ON(!valid_dma_direction(direction));
WARN_ON(nelems == 0 || sglist->length == 0);
for_each_sg(sglist, sg, nelems, i) {
dma_sync_single_for_device(dev, sg->dma_address,
sg_dma_len(sg), direction);
}
}
static inline int
tile_pci_dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
{
return 0;
}
static inline int
tile_pci_dma_supported(struct device *dev, u64 mask)
{
return 1;
}
static struct dma_map_ops tile_pci_default_dma_map_ops = {
.alloc = tile_pci_dma_alloc_coherent,
.free = tile_pci_dma_free_coherent,
.map_page = tile_pci_dma_map_page,
.unmap_page = tile_pci_dma_unmap_page,
.map_sg = tile_pci_dma_map_sg,
.unmap_sg = tile_pci_dma_unmap_sg,
.sync_single_for_cpu = tile_pci_dma_sync_single_for_cpu,
.sync_single_for_device = tile_pci_dma_sync_single_for_device,
.sync_sg_for_cpu = tile_pci_dma_sync_sg_for_cpu,
.sync_sg_for_device = tile_pci_dma_sync_sg_for_device,
.mapping_error = tile_pci_dma_mapping_error,
.dma_supported = tile_pci_dma_supported
};
struct dma_map_ops *gx_pci_dma_map_ops = &tile_pci_default_dma_map_ops;
EXPORT_SYMBOL(gx_pci_dma_map_ops);
/* PCI DMA mapping functions for legacy PCI devices */
#ifdef CONFIG_SWIOTLB
static void *tile_swiotlb_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
struct dma_attrs *attrs)
{
gfp |= GFP_DMA;
return swiotlb_alloc_coherent(dev, size, dma_handle, gfp);
}
static void tile_swiotlb_free_coherent(struct device *dev, size_t size,
void *vaddr, dma_addr_t dma_addr,
struct dma_attrs *attrs)
{
swiotlb_free_coherent(dev, size, vaddr, dma_addr);
}
static struct dma_map_ops pci_swiotlb_dma_ops = {
.alloc = tile_swiotlb_alloc_coherent,
.free = tile_swiotlb_free_coherent,
.map_page = swiotlb_map_page,
.unmap_page = swiotlb_unmap_page,
.map_sg = swiotlb_map_sg_attrs,
.unmap_sg = swiotlb_unmap_sg_attrs,
.sync_single_for_cpu = swiotlb_sync_single_for_cpu,
.sync_single_for_device = swiotlb_sync_single_for_device,
.sync_sg_for_cpu = swiotlb_sync_sg_for_cpu,
.sync_sg_for_device = swiotlb_sync_sg_for_device,
.dma_supported = swiotlb_dma_supported,
.mapping_error = swiotlb_dma_mapping_error,
};
struct dma_map_ops *gx_legacy_pci_dma_map_ops = &pci_swiotlb_dma_ops;
#else
struct dma_map_ops *gx_legacy_pci_dma_map_ops;
#endif
EXPORT_SYMBOL(gx_legacy_pci_dma_map_ops);
#ifdef CONFIG_ARCH_HAS_DMA_SET_COHERENT_MASK
int dma_set_coherent_mask(struct device *dev, u64 mask)
{
struct dma_map_ops *dma_ops = get_dma_ops(dev);
/* Handle legacy PCI devices with limited memory addressability. */
if (((dma_ops == gx_pci_dma_map_ops) ||
(dma_ops == gx_legacy_pci_dma_map_ops)) &&
(mask <= DMA_BIT_MASK(32))) {
if (mask > dev->archdata.max_direct_dma_addr)
mask = dev->archdata.max_direct_dma_addr;
}
if (!dma_supported(dev, mask))
return -EIO;
dev->coherent_dma_mask = mask;
return 0;
}
EXPORT_SYMBOL(dma_set_coherent_mask);
#endif

596
arch/tile/kernel/pci.c Normal file
View File

@@ -0,0 +1,596 @@
/*
* Copyright 2011 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/capability.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/bootmem.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/export.h>
#include <asm/processor.h>
#include <asm/sections.h>
#include <asm/byteorder.h>
#include <asm/hv_driver.h>
#include <hv/drv_pcie_rc_intf.h>
/*
* Initialization flow and process
* -------------------------------
*
* This files contains the routines to search for PCI buses,
* enumerate the buses, and configure any attached devices.
*
* There are two entry points here:
* 1) tile_pci_init
* This sets up the pci_controller structs, and opens the
* FDs to the hypervisor. This is called from setup_arch() early
* in the boot process.
* 2) pcibios_init
* This probes the PCI bus(es) for any attached hardware. It's
* called by subsys_initcall. All of the real work is done by the
* generic Linux PCI layer.
*
*/
/*
* This flag tells if the platform is TILEmpower that needs
* special configuration for the PLX switch chip.
*/
int __write_once tile_plx_gen1;
static struct pci_controller controllers[TILE_NUM_PCIE];
static int num_controllers;
static int pci_scan_flags[TILE_NUM_PCIE];
static struct pci_ops tile_cfg_ops;
/*
* We don't need to worry about the alignment of resources.
*/
resource_size_t pcibios_align_resource(void *data, const struct resource *res,
resource_size_t size, resource_size_t align)
{
return res->start;
}
EXPORT_SYMBOL(pcibios_align_resource);
/*
* Open a FD to the hypervisor PCI device.
*
* controller_id is the controller number, config type is 0 or 1 for
* config0 or config1 operations.
*/
static int tile_pcie_open(int controller_id, int config_type)
{
char filename[32];
int fd;
sprintf(filename, "pcie/%d/config%d", controller_id, config_type);
fd = hv_dev_open((HV_VirtAddr)filename, 0);
return fd;
}
/*
* Get the IRQ numbers from the HV and set up the handlers for them.
*/
static int tile_init_irqs(int controller_id, struct pci_controller *controller)
{
char filename[32];
int fd;
int ret;
int x;
struct pcie_rc_config rc_config;
sprintf(filename, "pcie/%d/ctl", controller_id);
fd = hv_dev_open((HV_VirtAddr)filename, 0);
if (fd < 0) {
pr_err("PCI: hv_dev_open(%s) failed\n", filename);
return -1;
}
ret = hv_dev_pread(fd, 0, (HV_VirtAddr)(&rc_config),
sizeof(rc_config), PCIE_RC_CONFIG_MASK_OFF);
hv_dev_close(fd);
if (ret != sizeof(rc_config)) {
pr_err("PCI: wanted %zd bytes, got %d\n",
sizeof(rc_config), ret);
return -1;
}
/* Record irq_base so that we can map INTx to IRQ # later. */
controller->irq_base = rc_config.intr;
for (x = 0; x < 4; x++)
tile_irq_activate(rc_config.intr + x,
TILE_IRQ_HW_CLEAR);
if (rc_config.plx_gen1)
controller->plx_gen1 = 1;
return 0;
}
/*
* First initialization entry point, called from setup_arch().
*
* Find valid controllers and fill in pci_controller structs for each
* of them.
*
* Returns the number of controllers discovered.
*/
int __init tile_pci_init(void)
{
int i;
pr_info("PCI: Searching for controllers...\n");
/* Re-init number of PCIe controllers to support hot-plug feature. */
num_controllers = 0;
/* Do any configuration we need before using the PCIe */
for (i = 0; i < TILE_NUM_PCIE; i++) {
/*
* To see whether we need a real config op based on
* the results of pcibios_init(), to support PCIe hot-plug.
*/
if (pci_scan_flags[i] == 0) {
int hv_cfg_fd0 = -1;
int hv_cfg_fd1 = -1;
int hv_mem_fd = -1;
char name[32];
struct pci_controller *controller;
/*
* Open the fd to the HV. If it fails then this
* device doesn't exist.
*/
hv_cfg_fd0 = tile_pcie_open(i, 0);
if (hv_cfg_fd0 < 0)
continue;
hv_cfg_fd1 = tile_pcie_open(i, 1);
if (hv_cfg_fd1 < 0) {
pr_err("PCI: Couldn't open config fd to HV "
"for controller %d\n", i);
goto err_cont;
}
sprintf(name, "pcie/%d/mem", i);
hv_mem_fd = hv_dev_open((HV_VirtAddr)name, 0);
if (hv_mem_fd < 0) {
pr_err("PCI: Could not open mem fd to HV!\n");
goto err_cont;
}
pr_info("PCI: Found PCI controller #%d\n", i);
controller = &controllers[i];
controller->index = i;
controller->hv_cfg_fd[0] = hv_cfg_fd0;
controller->hv_cfg_fd[1] = hv_cfg_fd1;
controller->hv_mem_fd = hv_mem_fd;
controller->first_busno = 0;
controller->last_busno = 0xff;
controller->ops = &tile_cfg_ops;
num_controllers++;
continue;
err_cont:
if (hv_cfg_fd0 >= 0)
hv_dev_close(hv_cfg_fd0);
if (hv_cfg_fd1 >= 0)
hv_dev_close(hv_cfg_fd1);
if (hv_mem_fd >= 0)
hv_dev_close(hv_mem_fd);
continue;
}
}
/*
* Before using the PCIe, see if we need to do any platform-specific
* configuration, such as the PLX switch Gen 1 issue on TILEmpower.
*/
for (i = 0; i < num_controllers; i++) {
struct pci_controller *controller = &controllers[i];
if (controller->plx_gen1)
tile_plx_gen1 = 1;
}
return num_controllers;
}
/*
* (pin - 1) converts from the PCI standard's [1:4] convention to
* a normal [0:3] range.
*/
static int tile_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
struct pci_controller *controller =
(struct pci_controller *)dev->sysdata;
return (pin - 1) + controller->irq_base;
}
static void fixup_read_and_payload_sizes(void)
{
struct pci_dev *dev = NULL;
int smallest_max_payload = 0x1; /* Tile maxes out at 256 bytes. */
int max_read_size = 0x2; /* Limit to 512 byte reads. */
u16 new_values;
/* Scan for the smallest maximum payload size. */
for_each_pci_dev(dev) {
u32 devcap;
int max_payload;
if (!pci_is_pcie(dev))
continue;
pcie_capability_read_dword(dev, PCI_EXP_DEVCAP, &devcap);
max_payload = devcap & PCI_EXP_DEVCAP_PAYLOAD;
if (max_payload < smallest_max_payload)
smallest_max_payload = max_payload;
}
/* Now, set the max_payload_size for all devices to that value. */
new_values = (max_read_size << 12) | (smallest_max_payload << 5);
for_each_pci_dev(dev)
pcie_capability_clear_and_set_word(dev, PCI_EXP_DEVCTL,
PCI_EXP_DEVCTL_PAYLOAD | PCI_EXP_DEVCTL_READRQ,
new_values);
}
/*
* Second PCI initialization entry point, called by subsys_initcall.
*
* The controllers have been set up by the time we get here, by a call to
* tile_pci_init.
*/
int __init pcibios_init(void)
{
int i;
pr_info("PCI: Probing PCI hardware\n");
/*
* Delay a bit in case devices aren't ready. Some devices are
* known to require at least 20ms here, but we use a more
* conservative value.
*/
mdelay(250);
/* Scan all of the recorded PCI controllers. */
for (i = 0; i < TILE_NUM_PCIE; i++) {
/*
* Do real pcibios init ops if the controller is initialized
* by tile_pci_init() successfully and not initialized by
* pcibios_init() yet to support PCIe hot-plug.
*/
if (pci_scan_flags[i] == 0 && controllers[i].ops != NULL) {
struct pci_controller *controller = &controllers[i];
struct pci_bus *bus;
LIST_HEAD(resources);
if (tile_init_irqs(i, controller)) {
pr_err("PCI: Could not initialize IRQs\n");
continue;
}
pr_info("PCI: initializing controller #%d\n", i);
/*
* This comes from the generic Linux PCI driver.
*
* It reads the PCI tree for this bus into the Linux
* data structures.
*
* This is inlined in linux/pci.h and calls into
* pci_scan_bus_parented() in probe.c.
*/
pci_add_resource(&resources, &ioport_resource);
pci_add_resource(&resources, &iomem_resource);
bus = pci_scan_root_bus(NULL, 0, controller->ops, controller, &resources);
controller->root_bus = bus;
controller->last_busno = bus->busn_res.end;
}
}
/* Do machine dependent PCI interrupt routing */
pci_fixup_irqs(pci_common_swizzle, tile_map_irq);
/*
* This comes from the generic Linux PCI driver.
*
* It allocates all of the resources (I/O memory, etc)
* associated with the devices read in above.
*/
pci_assign_unassigned_resources();
/* Configure the max_read_size and max_payload_size values. */
fixup_read_and_payload_sizes();
/* Record the I/O resources in the PCI controller structure. */
for (i = 0; i < TILE_NUM_PCIE; i++) {
/*
* Do real pcibios init ops if the controller is initialized
* by tile_pci_init() successfully and not initialized by
* pcibios_init() yet to support PCIe hot-plug.
*/
if (pci_scan_flags[i] == 0 && controllers[i].ops != NULL) {
struct pci_bus *root_bus = controllers[i].root_bus;
struct pci_bus *next_bus;
struct pci_dev *dev;
list_for_each_entry(dev, &root_bus->devices, bus_list) {
/*
* Find the PCI host controller, ie. the 1st
* bridge.
*/
if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI &&
(PCI_SLOT(dev->devfn) == 0)) {
next_bus = dev->subordinate;
controllers[i].mem_resources[0] =
*next_bus->resource[0];
controllers[i].mem_resources[1] =
*next_bus->resource[1];
controllers[i].mem_resources[2] =
*next_bus->resource[2];
/* Setup flags. */
pci_scan_flags[i] = 1;
break;
}
}
}
}
return 0;
}
subsys_initcall(pcibios_init);
/*
* No bus fixups needed.
*/
void pcibios_fixup_bus(struct pci_bus *bus)
{
/* Nothing needs to be done. */
}
void pcibios_set_master(struct pci_dev *dev)
{
/* No special bus mastering setup handling. */
}
/*
* Enable memory and/or address decoding, as appropriate, for the
* device described by the 'dev' struct.
*
* This is called from the generic PCI layer, and can be called
* for bridges or endpoints.
*/
int pcibios_enable_device(struct pci_dev *dev, int mask)
{
u16 cmd, old_cmd;
u8 header_type;
int i;
struct resource *r;
pci_read_config_byte(dev, PCI_HEADER_TYPE, &header_type);
pci_read_config_word(dev, PCI_COMMAND, &cmd);
old_cmd = cmd;
if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) {
/*
* For bridges, we enable both memory and I/O decoding
* in call cases.
*/
cmd |= PCI_COMMAND_IO;
cmd |= PCI_COMMAND_MEMORY;
} else {
/*
* For endpoints, we enable memory and/or I/O decoding
* only if they have a memory resource of that type.
*/
for (i = 0; i < 6; i++) {
r = &dev->resource[i];
if (r->flags & IORESOURCE_UNSET) {
pr_err("PCI: Device %s not available "
"because of resource collisions\n",
pci_name(dev));
return -EINVAL;
}
if (r->flags & IORESOURCE_IO)
cmd |= PCI_COMMAND_IO;
if (r->flags & IORESOURCE_MEM)
cmd |= PCI_COMMAND_MEMORY;
}
}
/*
* We only write the command if it changed.
*/
if (cmd != old_cmd)
pci_write_config_word(dev, PCI_COMMAND, cmd);
return 0;
}
/****************************************************************
*
* Tile PCI config space read/write routines
*
****************************************************************/
/*
* These are the normal read and write ops
* These are expanded with macros from pci_bus_read_config_byte() etc.
*
* devfn is the combined PCI slot & function.
*
* offset is in bytes, from the start of config space for the
* specified bus & slot.
*/
static int tile_cfg_read(struct pci_bus *bus, unsigned int devfn, int offset,
int size, u32 *val)
{
struct pci_controller *controller = bus->sysdata;
int busnum = bus->number & 0xff;
int slot = (devfn >> 3) & 0x1f;
int function = devfn & 0x7;
u32 addr;
int config_mode = 1;
/*
* There is no bridge between the Tile and bus 0, so we
* use config0 to talk to bus 0.
*
* If we're talking to a bus other than zero then we
* must have found a bridge.
*/
if (busnum == 0) {
/*
* We fake an empty slot for (busnum == 0) && (slot > 0),
* since there is only one slot on bus 0.
*/
if (slot) {
*val = 0xFFFFFFFF;
return 0;
}
config_mode = 0;
}
addr = busnum << 20; /* Bus in 27:20 */
addr |= slot << 15; /* Slot (device) in 19:15 */
addr |= function << 12; /* Function is in 14:12 */
addr |= (offset & 0xFFF); /* byte address in 0:11 */
return hv_dev_pread(controller->hv_cfg_fd[config_mode], 0,
(HV_VirtAddr)(val), size, addr);
}
/*
* See tile_cfg_read() for relevant comments.
* Note that "val" is the value to write, not a pointer to that value.
*/
static int tile_cfg_write(struct pci_bus *bus, unsigned int devfn, int offset,
int size, u32 val)
{
struct pci_controller *controller = bus->sysdata;
int busnum = bus->number & 0xff;
int slot = (devfn >> 3) & 0x1f;
int function = devfn & 0x7;
u32 addr;
int config_mode = 1;
HV_VirtAddr valp = (HV_VirtAddr)&val;
/*
* For bus 0 slot 0 we use config 0 accesses.
*/
if (busnum == 0) {
/*
* We fake an empty slot for (busnum == 0) && (slot > 0),
* since there is only one slot on bus 0.
*/
if (slot)
return 0;
config_mode = 0;
}
addr = busnum << 20; /* Bus in 27:20 */
addr |= slot << 15; /* Slot (device) in 19:15 */
addr |= function << 12; /* Function is in 14:12 */
addr |= (offset & 0xFFF); /* byte address in 0:11 */
#ifdef __BIG_ENDIAN
/* Point to the correct part of the 32-bit "val". */
valp += 4 - size;
#endif
return hv_dev_pwrite(controller->hv_cfg_fd[config_mode], 0,
valp, size, addr);
}
static struct pci_ops tile_cfg_ops = {
.read = tile_cfg_read,
.write = tile_cfg_write,
};
/*
* In the following, each PCI controller's mem_resources[1]
* represents its (non-prefetchable) PCI memory resource.
* mem_resources[0] and mem_resources[2] refer to its PCI I/O and
* prefetchable PCI memory resources, respectively.
* For more details, see pci_setup_bridge() in setup-bus.c.
* By comparing the target PCI memory address against the
* end address of controller 0, we can determine the controller
* that should accept the PCI memory access.
*/
#define TILE_READ(size, type) \
type _tile_read##size(unsigned long addr) \
{ \
type val; \
int idx = 0; \
if (addr > controllers[0].mem_resources[1].end && \
addr > controllers[0].mem_resources[2].end) \
idx = 1; \
if (hv_dev_pread(controllers[idx].hv_mem_fd, 0, \
(HV_VirtAddr)(&val), sizeof(type), addr)) \
pr_err("PCI: read %zd bytes at 0x%lX failed\n", \
sizeof(type), addr); \
return val; \
} \
EXPORT_SYMBOL(_tile_read##size)
TILE_READ(b, u8);
TILE_READ(w, u16);
TILE_READ(l, u32);
TILE_READ(q, u64);
#define TILE_WRITE(size, type) \
void _tile_write##size(type val, unsigned long addr) \
{ \
int idx = 0; \
if (addr > controllers[0].mem_resources[1].end && \
addr > controllers[0].mem_resources[2].end) \
idx = 1; \
if (hv_dev_pwrite(controllers[idx].hv_mem_fd, 0, \
(HV_VirtAddr)(&val), sizeof(type), addr)) \
pr_err("PCI: write %zd bytes at 0x%lX failed\n", \
sizeof(type), addr); \
} \
EXPORT_SYMBOL(_tile_write##size)
TILE_WRITE(b, u8);
TILE_WRITE(w, u16);
TILE_WRITE(l, u32);
TILE_WRITE(q, u64);

1527
arch/tile/kernel/pci_gx.c Normal file

File diff suppressed because it is too large Load Diff

163
arch/tile/kernel/proc.c Normal file
View File

@@ -0,0 +1,163 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/smp.h>
#include <linux/seq_file.h>
#include <linux/threads.h>
#include <linux/cpumask.h>
#include <linux/timex.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#include <linux/hardirq.h>
#include <linux/hugetlb.h>
#include <linux/mman.h>
#include <asm/unaligned.h>
#include <asm/pgtable.h>
#include <asm/processor.h>
#include <asm/sections.h>
#include <asm/homecache.h>
#include <asm/hardwall.h>
#include <arch/chip.h>
/*
* Support /proc/cpuinfo
*/
#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);
if (n == 0) {
char buf[NR_CPUS*5];
cpulist_scnprintf(buf, sizeof(buf), cpu_online_mask);
seq_printf(m, "cpu count\t: %d\n", num_online_cpus());
seq_printf(m, "cpu list\t: %s\n", buf);
seq_printf(m, "model name\t: %s\n", chip_model);
seq_printf(m, "flags\t\t:\n"); /* nothing for now */
seq_printf(m, "cpu MHz\t\t: %llu.%06llu\n",
get_clock_rate() / 1000000,
(get_clock_rate() % 1000000));
seq_printf(m, "bogomips\t: %lu.%02lu\n\n",
loops_per_jiffy/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100);
}
#ifdef CONFIG_SMP
if (!cpu_online(n))
return 0;
#endif
seq_printf(m, "processor\t: %d\n", n);
/* Print only num_online_cpus() blank lines total. */
if (cpumask_next(n, cpu_online_mask) < nr_cpu_ids)
seq_printf(m, "\n");
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 c_start(m, pos);
}
static void c_stop(struct seq_file *m, void *v)
{
}
const struct seq_operations cpuinfo_op = {
.start = c_start,
.next = c_next,
.stop = c_stop,
.show = show_cpuinfo,
};
/*
* Support /proc/tile directory
*/
static int __init proc_tile_init(void)
{
struct proc_dir_entry *root = proc_mkdir("tile", NULL);
if (root == NULL)
return 0;
proc_tile_hardwall_init(root);
return 0;
}
arch_initcall(proc_tile_init);
/*
* Support /proc/sys/tile directory
*/
#ifndef __tilegx__ /* FIXME: GX: no support for unaligned access yet */
static ctl_table unaligned_subtable[] = {
{
.procname = "enabled",
.data = &unaligned_fixup,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.procname = "printk",
.data = &unaligned_printk,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.procname = "count",
.data = &unaligned_fixup_count,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{}
};
static ctl_table unaligned_table[] = {
{
.procname = "unaligned_fixup",
.mode = 0555,
.child = unaligned_subtable
},
{}
};
static struct ctl_path tile_path[] = {
{ .procname = "tile" },
{ }
};
static int __init proc_sys_tile_init(void)
{
register_sysctl_paths(tile_path, unaligned_table);
return 0;
}
arch_initcall(proc_sys_tile_init);
#endif

598
arch/tile/kernel/process.c Normal file
View File

@@ -0,0 +1,598 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/sched.h>
#include <linux/preempt.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kprobes.h>
#include <linux/elfcore.h>
#include <linux/tick.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/compat.h>
#include <linux/hardirq.h>
#include <linux/syscalls.h>
#include <linux/kernel.h>
#include <linux/tracehook.h>
#include <linux/signal.h>
#include <asm/stack.h>
#include <asm/switch_to.h>
#include <asm/homecache.h>
#include <asm/syscalls.h>
#include <asm/traps.h>
#include <asm/setup.h>
#ifdef CONFIG_HARDWALL
#include <asm/hardwall.h>
#endif
#include <arch/chip.h>
#include <arch/abi.h>
#include <arch/sim_def.h>
/*
* Use the (x86) "idle=poll" option to prefer low latency when leaving the
* idle loop over low power while in the idle loop, e.g. if we have
* one thread per core and we want to get threads out of futex waits fast.
*/
static int __init idle_setup(char *str)
{
if (!str)
return -EINVAL;
if (!strcmp(str, "poll")) {
pr_info("using polling idle threads.\n");
cpu_idle_poll_ctrl(true);
return 0;
} else if (!strcmp(str, "halt")) {
return 0;
}
return -1;
}
early_param("idle", idle_setup);
void arch_cpu_idle(void)
{
__get_cpu_var(irq_stat).idle_timestamp = jiffies;
_cpu_idle();
}
/*
* Release a thread_info structure
*/
void arch_release_thread_info(struct thread_info *info)
{
struct single_step_state *step_state = info->step_state;
#ifdef CONFIG_HARDWALL
/*
* We free a thread_info from the context of the task that has
* been scheduled next, so the original task is already dead.
* Calling deactivate here just frees up the data structures.
* If the task we're freeing held the last reference to a
* hardwall fd, it would have been released prior to this point
* anyway via exit_files(), and the hardwall_task.info pointers
* would be NULL by now.
*/
hardwall_deactivate_all(info->task);
#endif
if (step_state) {
/*
* FIXME: we don't munmap step_state->buffer
* because the mm_struct for this process (info->task->mm)
* has already been zeroed in exit_mm(). Keeping a
* reference to it here seems like a bad move, so this
* means we can't munmap() the buffer, and therefore if we
* ptrace multiple threads in a process, we will slowly
* leak user memory. (Note that as soon as the last
* thread in a process dies, we will reclaim all user
* memory including single-step buffers in the usual way.)
* We should either assign a kernel VA to this buffer
* somehow, or we should associate the buffer(s) with the
* mm itself so we can clean them up that way.
*/
kfree(step_state);
}
}
static void save_arch_state(struct thread_struct *t);
int copy_thread(unsigned long clone_flags, unsigned long sp,
unsigned long arg, struct task_struct *p)
{
struct pt_regs *childregs = task_pt_regs(p);
unsigned long ksp;
unsigned long *callee_regs;
/*
* Set up the stack and stack pointer appropriately for the
* new child to find itself woken up in __switch_to().
* The callee-saved registers must be on the stack to be read;
* the new task will then jump to assembly support to handle
* calling schedule_tail(), etc., and (for userspace tasks)
* returning to the context set up in the pt_regs.
*/
ksp = (unsigned long) childregs;
ksp -= C_ABI_SAVE_AREA_SIZE; /* interrupt-entry save area */
((long *)ksp)[0] = ((long *)ksp)[1] = 0;
ksp -= CALLEE_SAVED_REGS_COUNT * sizeof(unsigned long);
callee_regs = (unsigned long *)ksp;
ksp -= C_ABI_SAVE_AREA_SIZE; /* __switch_to() save area */
((long *)ksp)[0] = ((long *)ksp)[1] = 0;
p->thread.ksp = ksp;
/* Record the pid of the task that created this one. */
p->thread.creator_pid = current->pid;
if (unlikely(p->flags & PF_KTHREAD)) {
/* kernel thread */
memset(childregs, 0, sizeof(struct pt_regs));
memset(&callee_regs[2], 0,
(CALLEE_SAVED_REGS_COUNT - 2) * sizeof(unsigned long));
callee_regs[0] = sp; /* r30 = function */
callee_regs[1] = arg; /* r31 = arg */
childregs->ex1 = PL_ICS_EX1(KERNEL_PL, 0);
p->thread.pc = (unsigned long) ret_from_kernel_thread;
return 0;
}
/*
* Start new thread in ret_from_fork so it schedules properly
* and then return from interrupt like the parent.
*/
p->thread.pc = (unsigned long) ret_from_fork;
/*
* Do not clone step state from the parent; each thread
* must make its own lazily.
*/
task_thread_info(p)->step_state = NULL;
/*
* Copy the registers onto the kernel stack so the
* return-from-interrupt code will reload it into registers.
*/
*childregs = *current_pt_regs();
childregs->regs[0] = 0; /* return value is zero */
if (sp)
childregs->sp = sp; /* override with new user stack pointer */
memcpy(callee_regs, &childregs->regs[CALLEE_SAVED_FIRST_REG],
CALLEE_SAVED_REGS_COUNT * sizeof(unsigned long));
/* Save user stack top pointer so we can ID the stack vm area later. */
p->thread.usp0 = childregs->sp;
/*
* If CLONE_SETTLS is set, set "tp" in the new task to "r4",
* which is passed in as arg #5 to sys_clone().
*/
if (clone_flags & CLONE_SETTLS)
childregs->tp = childregs->regs[4];
#if CHIP_HAS_TILE_DMA()
/*
* No DMA in the new thread. We model this on the fact that
* fork() clears the pending signals, alarms, and aio for the child.
*/
memset(&p->thread.tile_dma_state, 0, sizeof(struct tile_dma_state));
memset(&p->thread.dma_async_tlb, 0, sizeof(struct async_tlb));
#endif
#if CHIP_HAS_SN_PROC()
/* Likewise, the new thread is not running static processor code. */
p->thread.sn_proc_running = 0;
memset(&p->thread.sn_async_tlb, 0, sizeof(struct async_tlb));
#endif
#if CHIP_HAS_PROC_STATUS_SPR()
/* New thread has its miscellaneous processor state bits clear. */
p->thread.proc_status = 0;
#endif
#ifdef CONFIG_HARDWALL
/* New thread does not own any networks. */
memset(&p->thread.hardwall[0], 0,
sizeof(struct hardwall_task) * HARDWALL_TYPES);
#endif
/*
* Start the new thread with the current architecture state
* (user interrupt masks, etc.).
*/
save_arch_state(&p->thread);
return 0;
}
/*
* Return "current" if it looks plausible, or else a pointer to a dummy.
* This can be helpful if we are just trying to emit a clean panic.
*/
struct task_struct *validate_current(void)
{
static struct task_struct corrupt = { .comm = "<corrupt>" };
struct task_struct *tsk = current;
if (unlikely((unsigned long)tsk < PAGE_OFFSET ||
(high_memory && (void *)tsk > high_memory) ||
((unsigned long)tsk & (__alignof__(*tsk) - 1)) != 0)) {
pr_err("Corrupt 'current' %p (sp %#lx)\n", tsk, stack_pointer);
tsk = &corrupt;
}
return tsk;
}
/* Take and return the pointer to the previous task, for schedule_tail(). */
struct task_struct *sim_notify_fork(struct task_struct *prev)
{
struct task_struct *tsk = current;
__insn_mtspr(SPR_SIM_CONTROL, SIM_CONTROL_OS_FORK_PARENT |
(tsk->thread.creator_pid << _SIM_CONTROL_OPERATOR_BITS));
__insn_mtspr(SPR_SIM_CONTROL, SIM_CONTROL_OS_FORK |
(tsk->pid << _SIM_CONTROL_OPERATOR_BITS));
return prev;
}
int dump_task_regs(struct task_struct *tsk, elf_gregset_t *regs)
{
struct pt_regs *ptregs = task_pt_regs(tsk);
elf_core_copy_regs(regs, ptregs);
return 1;
}
#if CHIP_HAS_TILE_DMA()
/* Allow user processes to access the DMA SPRs */
void grant_dma_mpls(void)
{
#if CONFIG_KERNEL_PL == 2
__insn_mtspr(SPR_MPL_DMA_CPL_SET_1, 1);
__insn_mtspr(SPR_MPL_DMA_NOTIFY_SET_1, 1);
#else
__insn_mtspr(SPR_MPL_DMA_CPL_SET_0, 1);
__insn_mtspr(SPR_MPL_DMA_NOTIFY_SET_0, 1);
#endif
}
/* Forbid user processes from accessing the DMA SPRs */
void restrict_dma_mpls(void)
{
#if CONFIG_KERNEL_PL == 2
__insn_mtspr(SPR_MPL_DMA_CPL_SET_2, 1);
__insn_mtspr(SPR_MPL_DMA_NOTIFY_SET_2, 1);
#else
__insn_mtspr(SPR_MPL_DMA_CPL_SET_1, 1);
__insn_mtspr(SPR_MPL_DMA_NOTIFY_SET_1, 1);
#endif
}
/* Pause the DMA engine, then save off its state registers. */
static void save_tile_dma_state(struct tile_dma_state *dma)
{
unsigned long state = __insn_mfspr(SPR_DMA_USER_STATUS);
unsigned long post_suspend_state;
/* If we're running, suspend the engine. */
if ((state & DMA_STATUS_MASK) == SPR_DMA_STATUS__RUNNING_MASK)
__insn_mtspr(SPR_DMA_CTR, SPR_DMA_CTR__SUSPEND_MASK);
/*
* Wait for the engine to idle, then save regs. Note that we
* want to record the "running" bit from before suspension,
* and the "done" bit from after, so that we can properly
* distinguish a case where the user suspended the engine from
* the case where the kernel suspended as part of the context
* swap.
*/
do {
post_suspend_state = __insn_mfspr(SPR_DMA_USER_STATUS);
} while (post_suspend_state & SPR_DMA_STATUS__BUSY_MASK);
dma->src = __insn_mfspr(SPR_DMA_SRC_ADDR);
dma->src_chunk = __insn_mfspr(SPR_DMA_SRC_CHUNK_ADDR);
dma->dest = __insn_mfspr(SPR_DMA_DST_ADDR);
dma->dest_chunk = __insn_mfspr(SPR_DMA_DST_CHUNK_ADDR);
dma->strides = __insn_mfspr(SPR_DMA_STRIDE);
dma->chunk_size = __insn_mfspr(SPR_DMA_CHUNK_SIZE);
dma->byte = __insn_mfspr(SPR_DMA_BYTE);
dma->status = (state & SPR_DMA_STATUS__RUNNING_MASK) |
(post_suspend_state & SPR_DMA_STATUS__DONE_MASK);
}
/* Restart a DMA that was running before we were context-switched out. */
static void restore_tile_dma_state(struct thread_struct *t)
{
const struct tile_dma_state *dma = &t->tile_dma_state;
/*
* The only way to restore the done bit is to run a zero
* length transaction.
*/
if ((dma->status & SPR_DMA_STATUS__DONE_MASK) &&
!(__insn_mfspr(SPR_DMA_USER_STATUS) & SPR_DMA_STATUS__DONE_MASK)) {
__insn_mtspr(SPR_DMA_BYTE, 0);
__insn_mtspr(SPR_DMA_CTR, SPR_DMA_CTR__REQUEST_MASK);
while (__insn_mfspr(SPR_DMA_USER_STATUS) &
SPR_DMA_STATUS__BUSY_MASK)
;
}
__insn_mtspr(SPR_DMA_SRC_ADDR, dma->src);
__insn_mtspr(SPR_DMA_SRC_CHUNK_ADDR, dma->src_chunk);
__insn_mtspr(SPR_DMA_DST_ADDR, dma->dest);
__insn_mtspr(SPR_DMA_DST_CHUNK_ADDR, dma->dest_chunk);
__insn_mtspr(SPR_DMA_STRIDE, dma->strides);
__insn_mtspr(SPR_DMA_CHUNK_SIZE, dma->chunk_size);
__insn_mtspr(SPR_DMA_BYTE, dma->byte);
/*
* Restart the engine if we were running and not done.
* Clear a pending async DMA fault that we were waiting on return
* to user space to execute, since we expect the DMA engine
* to regenerate those faults for us now. Note that we don't
* try to clear the TIF_ASYNC_TLB flag, since it's relatively
* harmless if set, and it covers both DMA and the SN processor.
*/
if ((dma->status & DMA_STATUS_MASK) == SPR_DMA_STATUS__RUNNING_MASK) {
t->dma_async_tlb.fault_num = 0;
__insn_mtspr(SPR_DMA_CTR, SPR_DMA_CTR__REQUEST_MASK);
}
}
#endif
static void save_arch_state(struct thread_struct *t)
{
#if CHIP_HAS_SPLIT_INTR_MASK()
t->interrupt_mask = __insn_mfspr(SPR_INTERRUPT_MASK_0_0) |
((u64)__insn_mfspr(SPR_INTERRUPT_MASK_0_1) << 32);
#else
t->interrupt_mask = __insn_mfspr(SPR_INTERRUPT_MASK_0);
#endif
t->ex_context[0] = __insn_mfspr(SPR_EX_CONTEXT_0_0);
t->ex_context[1] = __insn_mfspr(SPR_EX_CONTEXT_0_1);
t->system_save[0] = __insn_mfspr(SPR_SYSTEM_SAVE_0_0);
t->system_save[1] = __insn_mfspr(SPR_SYSTEM_SAVE_0_1);
t->system_save[2] = __insn_mfspr(SPR_SYSTEM_SAVE_0_2);
t->system_save[3] = __insn_mfspr(SPR_SYSTEM_SAVE_0_3);
t->intctrl_0 = __insn_mfspr(SPR_INTCTRL_0_STATUS);
#if CHIP_HAS_PROC_STATUS_SPR()
t->proc_status = __insn_mfspr(SPR_PROC_STATUS);
#endif
#if !CHIP_HAS_FIXED_INTVEC_BASE()
t->interrupt_vector_base = __insn_mfspr(SPR_INTERRUPT_VECTOR_BASE_0);
#endif
#if CHIP_HAS_TILE_RTF_HWM()
t->tile_rtf_hwm = __insn_mfspr(SPR_TILE_RTF_HWM);
#endif
#if CHIP_HAS_DSTREAM_PF()
t->dstream_pf = __insn_mfspr(SPR_DSTREAM_PF);
#endif
}
static void restore_arch_state(const struct thread_struct *t)
{
#if CHIP_HAS_SPLIT_INTR_MASK()
__insn_mtspr(SPR_INTERRUPT_MASK_0_0, (u32) t->interrupt_mask);
__insn_mtspr(SPR_INTERRUPT_MASK_0_1, t->interrupt_mask >> 32);
#else
__insn_mtspr(SPR_INTERRUPT_MASK_0, t->interrupt_mask);
#endif
__insn_mtspr(SPR_EX_CONTEXT_0_0, t->ex_context[0]);
__insn_mtspr(SPR_EX_CONTEXT_0_1, t->ex_context[1]);
__insn_mtspr(SPR_SYSTEM_SAVE_0_0, t->system_save[0]);
__insn_mtspr(SPR_SYSTEM_SAVE_0_1, t->system_save[1]);
__insn_mtspr(SPR_SYSTEM_SAVE_0_2, t->system_save[2]);
__insn_mtspr(SPR_SYSTEM_SAVE_0_3, t->system_save[3]);
__insn_mtspr(SPR_INTCTRL_0_STATUS, t->intctrl_0);
#if CHIP_HAS_PROC_STATUS_SPR()
__insn_mtspr(SPR_PROC_STATUS, t->proc_status);
#endif
#if !CHIP_HAS_FIXED_INTVEC_BASE()
__insn_mtspr(SPR_INTERRUPT_VECTOR_BASE_0, t->interrupt_vector_base);
#endif
#if CHIP_HAS_TILE_RTF_HWM()
__insn_mtspr(SPR_TILE_RTF_HWM, t->tile_rtf_hwm);
#endif
#if CHIP_HAS_DSTREAM_PF()
__insn_mtspr(SPR_DSTREAM_PF, t->dstream_pf);
#endif
}
void _prepare_arch_switch(struct task_struct *next)
{
#if CHIP_HAS_SN_PROC()
int snctl;
#endif
#if CHIP_HAS_TILE_DMA()
struct tile_dma_state *dma = &current->thread.tile_dma_state;
if (dma->enabled)
save_tile_dma_state(dma);
#endif
#if CHIP_HAS_SN_PROC()
/*
* Suspend the static network processor if it was running.
* We do not suspend the fabric itself, just like we don't
* try to suspend the UDN.
*/
snctl = __insn_mfspr(SPR_SNCTL);
current->thread.sn_proc_running =
(snctl & SPR_SNCTL__FRZPROC_MASK) == 0;
if (current->thread.sn_proc_running)
__insn_mtspr(SPR_SNCTL, snctl | SPR_SNCTL__FRZPROC_MASK);
#endif
}
struct task_struct *__sched _switch_to(struct task_struct *prev,
struct task_struct *next)
{
/* DMA state is already saved; save off other arch state. */
save_arch_state(&prev->thread);
#if CHIP_HAS_TILE_DMA()
/*
* Restore DMA in new task if desired.
* Note that it is only safe to restart here since interrupts
* are disabled, so we can't take any DMATLB miss or access
* interrupts before we have finished switching stacks.
*/
if (next->thread.tile_dma_state.enabled) {
restore_tile_dma_state(&next->thread);
grant_dma_mpls();
} else {
restrict_dma_mpls();
}
#endif
/* Restore other arch state. */
restore_arch_state(&next->thread);
#if CHIP_HAS_SN_PROC()
/*
* Restart static network processor in the new process
* if it was running before.
*/
if (next->thread.sn_proc_running) {
int snctl = __insn_mfspr(SPR_SNCTL);
__insn_mtspr(SPR_SNCTL, snctl & ~SPR_SNCTL__FRZPROC_MASK);
}
#endif
#ifdef CONFIG_HARDWALL
/* Enable or disable access to the network registers appropriately. */
hardwall_switch_tasks(prev, next);
#endif
/*
* Switch kernel SP, PC, and callee-saved registers.
* In the context of the new task, return the old task pointer
* (i.e. the task that actually called __switch_to).
* Pass the value to use for SYSTEM_SAVE_K_0 when we reset our sp.
*/
return __switch_to(prev, next, next_current_ksp0(next));
}
/*
* This routine is called on return from interrupt if any of the
* TIF_WORK_MASK flags are set in thread_info->flags. It is
* entered with interrupts disabled so we don't miss an event
* that modified the thread_info flags. If any flag is set, we
* handle it and return, and the calling assembly code will
* re-disable interrupts, reload the thread flags, and call back
* if more flags need to be handled.
*
* We return whether we need to check the thread_info flags again
* or not. Note that we don't clear TIF_SINGLESTEP here, so it's
* important that it be tested last, and then claim that we don't
* need to recheck the flags.
*/
int do_work_pending(struct pt_regs *regs, u32 thread_info_flags)
{
/* If we enter in kernel mode, do nothing and exit the caller loop. */
if (!user_mode(regs))
return 0;
/* Enable interrupts; they are disabled again on return to caller. */
local_irq_enable();
if (thread_info_flags & _TIF_NEED_RESCHED) {
schedule();
return 1;
}
#if CHIP_HAS_TILE_DMA() || CHIP_HAS_SN_PROC()
if (thread_info_flags & _TIF_ASYNC_TLB) {
do_async_page_fault(regs);
return 1;
}
#endif
if (thread_info_flags & _TIF_SIGPENDING) {
do_signal(regs);
return 1;
}
if (thread_info_flags & _TIF_NOTIFY_RESUME) {
clear_thread_flag(TIF_NOTIFY_RESUME);
tracehook_notify_resume(regs);
return 1;
}
if (thread_info_flags & _TIF_SINGLESTEP) {
single_step_once(regs);
return 0;
}
panic("work_pending: bad flags %#x\n", thread_info_flags);
}
unsigned long get_wchan(struct task_struct *p)
{
struct KBacktraceIterator kbt;
if (!p || p == current || p->state == TASK_RUNNING)
return 0;
for (KBacktraceIterator_init(&kbt, p, NULL);
!KBacktraceIterator_end(&kbt);
KBacktraceIterator_next(&kbt)) {
if (!in_sched_functions(kbt.it.pc))
return kbt.it.pc;
}
return 0;
}
/* Flush thread state. */
void flush_thread(void)
{
/* Nothing */
}
/*
* Free current thread data structures etc..
*/
void exit_thread(void)
{
/* Nothing */
}
void show_regs(struct pt_regs *regs)
{
struct task_struct *tsk = validate_current();
int i;
pr_err("\n");
show_regs_print_info(KERN_ERR);
#ifdef __tilegx__
for (i = 0; i < 51; i += 3)
pr_err(" r%-2d: "REGFMT" r%-2d: "REGFMT" r%-2d: "REGFMT"\n",
i, regs->regs[i], i+1, regs->regs[i+1],
i+2, regs->regs[i+2]);
pr_err(" r51: "REGFMT" r52: "REGFMT" tp : "REGFMT"\n",
regs->regs[51], regs->regs[52], regs->tp);
pr_err(" sp : "REGFMT" lr : "REGFMT"\n", regs->sp, regs->lr);
#else
for (i = 0; i < 52; i += 4)
pr_err(" r%-2d: "REGFMT" r%-2d: "REGFMT
" r%-2d: "REGFMT" r%-2d: "REGFMT"\n",
i, regs->regs[i], i+1, regs->regs[i+1],
i+2, regs->regs[i+2], i+3, regs->regs[i+3]);
pr_err(" r52: "REGFMT" tp : "REGFMT" sp : "REGFMT" lr : "REGFMT"\n",
regs->regs[52], regs->tp, regs->sp, regs->lr);
#endif
pr_err(" pc : "REGFMT" ex1: %ld faultnum: %ld\n",
regs->pc, regs->ex1, regs->faultnum);
dump_stack_regs(regs);
}

292
arch/tile/kernel/ptrace.c Normal file
View File

@@ -0,0 +1,292 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* Copied from i386: Ross Biro 1/23/92
*/
#include <linux/kernel.h>
#include <linux/ptrace.h>
#include <linux/kprobes.h>
#include <linux/compat.h>
#include <linux/uaccess.h>
#include <linux/regset.h>
#include <linux/elf.h>
#include <linux/tracehook.h>
#include <asm/traps.h>
#include <arch/chip.h>
#define CREATE_TRACE_POINTS
#include <trace/events/syscalls.h>
void user_enable_single_step(struct task_struct *child)
{
set_tsk_thread_flag(child, TIF_SINGLESTEP);
}
void user_disable_single_step(struct task_struct *child)
{
clear_tsk_thread_flag(child, TIF_SINGLESTEP);
}
/*
* Called by kernel/ptrace.c when detaching..
*/
void ptrace_disable(struct task_struct *child)
{
clear_tsk_thread_flag(child, TIF_SINGLESTEP);
/*
* These two are currently unused, but will be set by arch_ptrace()
* and used in the syscall assembly when we do support them.
*/
clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
}
/*
* Get registers from task and ready the result for userspace.
* Note that we localize the API issues to getregs() and putregs() at
* some cost in performance, e.g. we need a full pt_regs copy for
* PEEKUSR, and two copies for POKEUSR. But in general we expect
* GETREGS/PUTREGS to be the API of choice anyway.
*/
static char *getregs(struct task_struct *child, struct pt_regs *uregs)
{
*uregs = *task_pt_regs(child);
/* Set up flags ABI bits. */
uregs->flags = 0;
#ifdef CONFIG_COMPAT
if (task_thread_info(child)->status & TS_COMPAT)
uregs->flags |= PT_FLAGS_COMPAT;
#endif
return (char *)uregs;
}
/* Put registers back to task. */
static void putregs(struct task_struct *child, struct pt_regs *uregs)
{
struct pt_regs *regs = task_pt_regs(child);
/* Don't allow overwriting the kernel-internal flags word. */
uregs->flags = regs->flags;
/* Only allow setting the ICS bit in the ex1 word. */
uregs->ex1 = PL_ICS_EX1(USER_PL, EX1_ICS(uregs->ex1));
*regs = *uregs;
}
enum tile_regset {
REGSET_GPR,
};
static int tile_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;
getregs(target, &regs);
return user_regset_copyout(&pos, &count, &kbuf, &ubuf, &regs, 0,
sizeof(regs));
}
static int tile_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;
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &regs, 0,
sizeof(regs));
if (ret)
return ret;
putregs(target, &regs);
return 0;
}
static const struct user_regset tile_user_regset[] = {
[REGSET_GPR] = {
.core_note_type = NT_PRSTATUS,
.n = ELF_NGREG,
.size = sizeof(elf_greg_t),
.align = sizeof(elf_greg_t),
.get = tile_gpr_get,
.set = tile_gpr_set,
},
};
static const struct user_regset_view tile_user_regset_view = {
.name = CHIP_ARCH_NAME,
.e_machine = ELF_ARCH,
.ei_osabi = ELF_OSABI,
.regsets = tile_user_regset,
.n = ARRAY_SIZE(tile_user_regset),
};
const struct user_regset_view *task_user_regset_view(struct task_struct *task)
{
return &tile_user_regset_view;
}
long arch_ptrace(struct task_struct *child, long request,
unsigned long addr, unsigned long data)
{
unsigned long __user *datap = (long __user __force *)data;
unsigned long tmp;
long ret = -EIO;
char *childreg;
struct pt_regs copyregs;
switch (request) {
case PTRACE_PEEKUSR: /* Read register from pt_regs. */
if (addr >= PTREGS_SIZE)
break;
childreg = getregs(child, &copyregs) + addr;
#ifdef CONFIG_COMPAT
if (is_compat_task()) {
if (addr & (sizeof(compat_long_t)-1))
break;
ret = put_user(*(compat_long_t *)childreg,
(compat_long_t __user *)datap);
} else
#endif
{
if (addr & (sizeof(long)-1))
break;
ret = put_user(*(long *)childreg, datap);
}
break;
case PTRACE_POKEUSR: /* Write register in pt_regs. */
if (addr >= PTREGS_SIZE)
break;
childreg = getregs(child, &copyregs) + addr;
#ifdef CONFIG_COMPAT
if (is_compat_task()) {
if (addr & (sizeof(compat_long_t)-1))
break;
*(compat_long_t *)childreg = data;
} else
#endif
{
if (addr & (sizeof(long)-1))
break;
*(long *)childreg = data;
}
putregs(child, &copyregs);
ret = 0;
break;
case PTRACE_GETREGS: /* Get all registers from the child. */
ret = copy_regset_to_user(child, &tile_user_regset_view,
REGSET_GPR, 0,
sizeof(struct pt_regs), datap);
break;
case PTRACE_SETREGS: /* Set all registers in the child. */
ret = copy_regset_from_user(child, &tile_user_regset_view,
REGSET_GPR, 0,
sizeof(struct pt_regs), datap);
break;
case PTRACE_GETFPREGS: /* Get the child FPU state. */
case PTRACE_SETFPREGS: /* Set the child FPU state. */
break;
case PTRACE_SETOPTIONS:
/* Support TILE-specific ptrace options. */
BUILD_BUG_ON(PTRACE_O_MASK_TILE & PTRACE_O_MASK);
tmp = data & PTRACE_O_MASK_TILE;
data &= ~PTRACE_O_MASK_TILE;
ret = ptrace_request(child, request, addr, data);
if (ret == 0) {
unsigned int flags = child->ptrace;
flags &= ~(PTRACE_O_MASK_TILE << PT_OPT_FLAG_SHIFT);
flags |= (tmp << PT_OPT_FLAG_SHIFT);
child->ptrace = flags;
}
break;
default:
#ifdef CONFIG_COMPAT
if (task_thread_info(current)->status & TS_COMPAT) {
ret = compat_ptrace_request(child, request,
addr, data);
break;
}
#endif
ret = ptrace_request(child, request, addr, data);
break;
}
return ret;
}
#ifdef CONFIG_COMPAT
/* Not used; we handle compat issues in arch_ptrace() directly. */
long compat_arch_ptrace(struct task_struct *child, compat_long_t request,
compat_ulong_t addr, compat_ulong_t data)
{
BUG();
}
#endif
int do_syscall_trace_enter(struct pt_regs *regs)
{
if (test_thread_flag(TIF_SYSCALL_TRACE)) {
if (tracehook_report_syscall_entry(regs))
regs->regs[TREG_SYSCALL_NR] = -1;
}
if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
trace_sys_enter(regs, regs->regs[TREG_SYSCALL_NR]);
return regs->regs[TREG_SYSCALL_NR];
}
void do_syscall_trace_exit(struct pt_regs *regs)
{
if (test_thread_flag(TIF_SYSCALL_TRACE))
tracehook_report_syscall_exit(regs, 0);
if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
trace_sys_exit(regs, regs->regs[0]);
}
void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs, int error_code)
{
struct siginfo info;
memset(&info, 0, sizeof(info));
info.si_signo = SIGTRAP;
info.si_code = TRAP_BRKPT;
info.si_addr = (void __user *) regs->pc;
/* Send us the fakey SIGTRAP */
force_sig_info(SIGTRAP, &info, tsk);
}
/* Handle synthetic interrupt delivered only by the simulator. */
void __kprobes do_breakpoint(struct pt_regs* regs, int fault_num)
{
send_sigtrap(current, regs, fault_num);
}

53
arch/tile/kernel/reboot.c Normal file
View File

@@ -0,0 +1,53 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/stddef.h>
#include <linux/reboot.h>
#include <linux/smp.h>
#include <linux/pm.h>
#include <linux/export.h>
#include <asm/page.h>
#include <asm/setup.h>
#include <hv/hypervisor.h>
#ifndef CONFIG_SMP
#define smp_send_stop()
#endif
void machine_halt(void)
{
warn_early_printk();
arch_local_irq_disable_all();
smp_send_stop();
hv_halt();
}
void machine_power_off(void)
{
warn_early_printk();
arch_local_irq_disable_all();
smp_send_stop();
hv_power_off();
}
void machine_restart(char *cmd)
{
arch_local_irq_disable_all();
smp_send_stop();
hv_restart((HV_VirtAddr) "vmlinux", (HV_VirtAddr) cmd);
}
/* No interesting distinction to be made here. */
void (*pm_power_off)(void) = NULL;
EXPORT_SYMBOL(pm_power_off);

145
arch/tile/kernel/regs_32.S Normal file
View File

@@ -0,0 +1,145 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/linkage.h>
#include <asm/ptrace.h>
#include <asm/asm-offsets.h>
#include <arch/spr_def.h>
#include <asm/processor.h>
#include <asm/switch_to.h>
/*
* See <asm/system.h>; called with prev and next task_struct pointers.
* "prev" is returned in r0 for _switch_to and also for ret_from_fork.
*
* We want to save pc/sp in "prev", and get the new pc/sp from "next".
* We also need to save all the callee-saved registers on the stack.
*
* Intel enables/disables access to the hardware cycle counter in
* seccomp (secure computing) environments if necessary, based on
* has_secure_computing(). We might want to do this at some point,
* though it would require virtualizing the other SPRs under WORLD_ACCESS.
*
* Since we're saving to the stack, we omit sp from this list.
* And for parallels with other architectures, we save lr separately,
* in the thread_struct itself (as the "pc" field).
*
* This code also needs to be aligned with process.c copy_thread()
*/
#if CALLEE_SAVED_REGS_COUNT != 24
# error Mismatch between <asm/system.h> and kernel/entry.S
#endif
#define FRAME_SIZE ((2 + CALLEE_SAVED_REGS_COUNT) * 4)
#define SAVE_REG(r) { sw r12, r; addi r12, r12, 4 }
#define LOAD_REG(r) { lw r, r12; addi r12, r12, 4 }
#define FOR_EACH_CALLEE_SAVED_REG(f) \
f(r30); f(r31); \
f(r32); f(r33); f(r34); f(r35); f(r36); f(r37); f(r38); f(r39); \
f(r40); f(r41); f(r42); f(r43); f(r44); f(r45); f(r46); f(r47); \
f(r48); f(r49); f(r50); f(r51); f(r52);
STD_ENTRY_SECTION(__switch_to, .sched.text)
{
move r10, sp
sw sp, lr
addi sp, sp, -FRAME_SIZE
}
{
addi r11, sp, 4
addi r12, sp, 8
}
{
sw r11, r10
addli r4, r1, TASK_STRUCT_THREAD_KSP_OFFSET
}
{
lw r13, r4 /* Load new sp to a temp register early. */
addli r3, r0, TASK_STRUCT_THREAD_KSP_OFFSET
}
FOR_EACH_CALLEE_SAVED_REG(SAVE_REG)
{
sw r3, sp
addli r3, r0, TASK_STRUCT_THREAD_PC_OFFSET
}
{
sw r3, lr
addli r4, r1, TASK_STRUCT_THREAD_PC_OFFSET
}
{
lw lr, r4
addi r12, r13, 8
}
{
/* Update sp and ksp0 simultaneously to avoid backtracer warnings. */
move sp, r13
mtspr SPR_SYSTEM_SAVE_K_0, r2
}
FOR_EACH_CALLEE_SAVED_REG(LOAD_REG)
.L__switch_to_pc:
{
addi sp, sp, FRAME_SIZE
jrp lr /* r0 is still valid here, so return it */
}
STD_ENDPROC(__switch_to)
/* Return a suitable address for the backtracer for suspended threads */
STD_ENTRY_SECTION(get_switch_to_pc, .sched.text)
lnk r0
{
addli r0, r0, .L__switch_to_pc - .
jrp lr
}
STD_ENDPROC(get_switch_to_pc)
STD_ENTRY(get_pt_regs)
.irp reg, r0, r1, r2, r3, r4, r5, r6, r7, \
r8, r9, r10, r11, r12, r13, r14, r15, \
r16, r17, r18, r19, r20, r21, r22, r23, \
r24, r25, r26, r27, r28, r29, r30, r31, \
r32, r33, r34, r35, r36, r37, r38, r39, \
r40, r41, r42, r43, r44, r45, r46, r47, \
r48, r49, r50, r51, r52, tp, sp
{
sw r0, \reg
addi r0, r0, 4
}
.endr
{
sw r0, lr
addi r0, r0, PTREGS_OFFSET_PC - PTREGS_OFFSET_LR
}
lnk r1
{
sw r0, r1
addi r0, r0, PTREGS_OFFSET_EX1 - PTREGS_OFFSET_PC
}
mfspr r1, INTERRUPT_CRITICAL_SECTION
shli r1, r1, SPR_EX_CONTEXT_1_1__ICS_SHIFT
ori r1, r1, KERNEL_PL
{
sw r0, r1
addi r0, r0, PTREGS_OFFSET_FAULTNUM - PTREGS_OFFSET_EX1
}
{
sw r0, zero /* clear faultnum */
addi r0, r0, PTREGS_OFFSET_ORIG_R0 - PTREGS_OFFSET_FAULTNUM
}
{
sw r0, zero /* clear orig_r0 */
addli r0, r0, -PTREGS_OFFSET_ORIG_R0 /* restore r0 to base */
}
jrp lr
STD_ENDPROC(get_pt_regs)

145
arch/tile/kernel/regs_64.S Normal file
View File

@@ -0,0 +1,145 @@
/*
* Copyright 2011 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/linkage.h>
#include <asm/ptrace.h>
#include <asm/asm-offsets.h>
#include <arch/spr_def.h>
#include <asm/processor.h>
#include <asm/switch_to.h>
/*
* See <asm/system.h>; called with prev and next task_struct pointers.
* "prev" is returned in r0 for _switch_to and also for ret_from_fork.
*
* We want to save pc/sp in "prev", and get the new pc/sp from "next".
* We also need to save all the callee-saved registers on the stack.
*
* Intel enables/disables access to the hardware cycle counter in
* seccomp (secure computing) environments if necessary, based on
* has_secure_computing(). We might want to do this at some point,
* though it would require virtualizing the other SPRs under WORLD_ACCESS.
*
* Since we're saving to the stack, we omit sp from this list.
* And for parallels with other architectures, we save lr separately,
* in the thread_struct itself (as the "pc" field).
*
* This code also needs to be aligned with process.c copy_thread()
*/
#if CALLEE_SAVED_REGS_COUNT != 24
# error Mismatch between <asm/system.h> and kernel/entry.S
#endif
#define FRAME_SIZE ((2 + CALLEE_SAVED_REGS_COUNT) * 8)
#define SAVE_REG(r) { st r12, r; addi r12, r12, 8 }
#define LOAD_REG(r) { ld r, r12; addi r12, r12, 8 }
#define FOR_EACH_CALLEE_SAVED_REG(f) \
f(r30); f(r31); \
f(r32); f(r33); f(r34); f(r35); f(r36); f(r37); f(r38); f(r39); \
f(r40); f(r41); f(r42); f(r43); f(r44); f(r45); f(r46); f(r47); \
f(r48); f(r49); f(r50); f(r51); f(r52);
STD_ENTRY_SECTION(__switch_to, .sched.text)
{
move r10, sp
st sp, lr
}
{
addli r11, sp, -FRAME_SIZE + 8
addli sp, sp, -FRAME_SIZE
}
{
st r11, r10
addli r4, r1, TASK_STRUCT_THREAD_KSP_OFFSET
}
{
ld r13, r4 /* Load new sp to a temp register early. */
addi r12, sp, 16
}
FOR_EACH_CALLEE_SAVED_REG(SAVE_REG)
addli r3, r0, TASK_STRUCT_THREAD_KSP_OFFSET
{
st r3, sp
addli r3, r0, TASK_STRUCT_THREAD_PC_OFFSET
}
{
st r3, lr
addli r4, r1, TASK_STRUCT_THREAD_PC_OFFSET
}
{
ld lr, r4
addi r12, r13, 16
}
{
/* Update sp and ksp0 simultaneously to avoid backtracer warnings. */
move sp, r13
mtspr SPR_SYSTEM_SAVE_K_0, r2
}
FOR_EACH_CALLEE_SAVED_REG(LOAD_REG)
.L__switch_to_pc:
{
addli sp, sp, FRAME_SIZE
jrp lr /* r0 is still valid here, so return it */
}
STD_ENDPROC(__switch_to)
/* Return a suitable address for the backtracer for suspended threads */
STD_ENTRY_SECTION(get_switch_to_pc, .sched.text)
lnk r0
{
addli r0, r0, .L__switch_to_pc - .
jrp lr
}
STD_ENDPROC(get_switch_to_pc)
STD_ENTRY(get_pt_regs)
.irp reg, r0, r1, r2, r3, r4, r5, r6, r7, \
r8, r9, r10, r11, r12, r13, r14, r15, \
r16, r17, r18, r19, r20, r21, r22, r23, \
r24, r25, r26, r27, r28, r29, r30, r31, \
r32, r33, r34, r35, r36, r37, r38, r39, \
r40, r41, r42, r43, r44, r45, r46, r47, \
r48, r49, r50, r51, r52, tp, sp
{
st r0, \reg
addi r0, r0, 8
}
.endr
{
st r0, lr
addi r0, r0, PTREGS_OFFSET_PC - PTREGS_OFFSET_LR
}
lnk r1
{
st r0, r1
addi r0, r0, PTREGS_OFFSET_EX1 - PTREGS_OFFSET_PC
}
mfspr r1, INTERRUPT_CRITICAL_SECTION
shli r1, r1, SPR_EX_CONTEXT_1_1__ICS_SHIFT
ori r1, r1, KERNEL_PL
{
st r0, r1
addi r0, r0, PTREGS_OFFSET_FAULTNUM - PTREGS_OFFSET_EX1
}
{
st r0, zero /* clear faultnum */
addi r0, r0, PTREGS_OFFSET_ORIG_R0 - PTREGS_OFFSET_FAULTNUM
}
{
st r0, zero /* clear orig_r0 */
addli r0, r0, -PTREGS_OFFSET_ORIG_R0 /* restore r0 to base */
}
jrp lr
STD_ENDPROC(get_pt_regs)

View File

@@ -0,0 +1,280 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* copy new kernel into place and then call hv_reexec
*
*/
#include <linux/linkage.h>
#include <arch/chip.h>
#include <asm/page.h>
#include <hv/hypervisor.h>
#define ___hvb MEM_SV_INTRPT + HV_GLUE_START_CPA
#define ___hv_dispatch(f) (___hvb + (HV_DISPATCH_ENTRY_SIZE * f))
#define ___hv_console_putc ___hv_dispatch(HV_DISPATCH_CONSOLE_PUTC)
#define ___hv_halt ___hv_dispatch(HV_DISPATCH_HALT)
#define ___hv_reexec ___hv_dispatch(HV_DISPATCH_REEXEC)
#define ___hv_flush_remote ___hv_dispatch(HV_DISPATCH_FLUSH_REMOTE)
#undef RELOCATE_NEW_KERNEL_VERBOSE
STD_ENTRY(relocate_new_kernel)
move r30, r0 /* page list */
move r31, r1 /* address of page we are on */
move r32, r2 /* start address of new kernel */
shri r1, r1, PAGE_SHIFT
addi r1, r1, 1
shli sp, r1, PAGE_SHIFT
addi sp, sp, -8
/* we now have a stack (whether we need one or not) */
moveli r40, lo16(___hv_console_putc)
auli r40, r40, ha16(___hv_console_putc)
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 'r'
jalr r40
moveli r0, '_'
jalr r40
moveli r0, 'n'
jalr r40
moveli r0, '_'
jalr r40
moveli r0, 'k'
jalr r40
moveli r0, '\n'
jalr r40
#endif
/*
* Throughout this code r30 is pointer to the element of page
* list we are working on.
*
* Normally we get to the next element of the page list by
* incrementing r30 by four. The exception is if the element
* on the page list is an IND_INDIRECTION in which case we use
* the element with the low bits masked off as the new value
* of r30.
*
* To get this started, we need the value passed to us (which
* will always be an IND_INDIRECTION) in memory somewhere with
* r30 pointing at it. To do that, we push the value passed
* to us on the stack and make r30 point to it.
*/
sw sp, r30
move r30, sp
addi sp, sp, -8
#if CHIP_HAS_CBOX_HOME_MAP()
/*
* On TILEPro, we need to flush all tiles' caches, since we may
* have been doing hash-for-home caching there. Note that we
* must do this _after_ we're completely done modifying any memory
* other than our output buffer (which we know is locally cached).
* We want the caches to be fully clean when we do the reexec,
* because the hypervisor is going to do this flush again at that
* point, and we don't want that second flush to overwrite any memory.
*/
{
move r0, zero /* cache_pa */
move r1, zero
}
{
auli r2, zero, ha16(HV_FLUSH_EVICT_L2) /* cache_control */
movei r3, -1 /* cache_cpumask; -1 means all client tiles */
}
{
move r4, zero /* tlb_va */
move r5, zero /* tlb_length */
}
{
move r6, zero /* tlb_pgsize */
move r7, zero /* tlb_cpumask */
}
{
move r8, zero /* asids */
moveli r20, lo16(___hv_flush_remote)
}
{
move r9, zero /* asidcount */
auli r20, r20, ha16(___hv_flush_remote)
}
jalr r20
#endif
/* r33 is destination pointer, default to zero */
moveli r33, 0
.Lloop: lw r10, r30
andi r9, r10, 0xf /* low 4 bits tell us what type it is */
xor r10, r10, r9 /* r10 is now value with low 4 bits stripped */
seqi r0, r9, 0x1 /* IND_DESTINATION */
bzt r0, .Ltry2
move r33, r10
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 'd'
jalr r40
#endif
addi r30, r30, 4
j .Lloop
.Ltry2:
seqi r0, r9, 0x2 /* IND_INDIRECTION */
bzt r0, .Ltry4
move r30, r10
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 'i'
jalr r40
#endif
j .Lloop
.Ltry4:
seqi r0, r9, 0x4 /* IND_DONE */
bzt r0, .Ltry8
mf
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 'D'
jalr r40
moveli r0, '\n'
jalr r40
#endif
move r0, r32
moveli r1, 0 /* arg to hv_reexec is 64 bits */
moveli r41, lo16(___hv_reexec)
auli r41, r41, ha16(___hv_reexec)
jalr r41
/* we should not get here */
moveli r0, '?'
jalr r40
moveli r0, '\n'
jalr r40
j .Lhalt
.Ltry8: seqi r0, r9, 0x8 /* IND_SOURCE */
bz r0, .Lerr /* unknown type */
/* copy page at r10 to page at r33 */
move r11, r33
moveli r0, lo16(PAGE_SIZE)
auli r0, r0, ha16(PAGE_SIZE)
add r33, r33, r0
/* copy word at r10 to word at r11 until r11 equals r33 */
/* We know page size must be multiple of 16, so we can unroll
* 16 times safely without any edge case checking.
*
* Issue a flush of the destination every 16 words to avoid
* incoherence when starting the new kernel. (Now this is
* just good paranoia because the hv_reexec call will also
* take care of this.)
*/
1:
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0; addi r11, r11, 4 }
{ lw r0, r10; addi r10, r10, 4 }
{ sw r11, r0 }
{ flush r11 ; addi r11, r11, 4 }
seq r0, r33, r11
bzt r0, 1b
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 's'
jalr r40
#endif
addi r30, r30, 4
j .Lloop
.Lerr: moveli r0, 'e'
jalr r40
moveli r0, 'r'
jalr r40
moveli r0, 'r'
jalr r40
moveli r0, '\n'
jalr r40
.Lhalt:
moveli r41, lo16(___hv_halt)
auli r41, r41, ha16(___hv_halt)
jalr r41
STD_ENDPROC(relocate_new_kernel)
.section .rodata,"a"
.globl relocate_new_kernel_size
relocate_new_kernel_size:
.long .Lend_relocate_new_kernel - relocate_new_kernel

View File

@@ -0,0 +1,260 @@
/*
* Copyright 2011 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* copy new kernel into place and then call hv_reexec
*
*/
#include <linux/linkage.h>
#include <arch/chip.h>
#include <asm/page.h>
#include <hv/hypervisor.h>
#undef RELOCATE_NEW_KERNEL_VERBOSE
STD_ENTRY(relocate_new_kernel)
move r30, r0 /* page list */
move r31, r1 /* address of page we are on */
move r32, r2 /* start address of new kernel */
shrui r1, r1, PAGE_SHIFT
addi r1, r1, 1
shli sp, r1, PAGE_SHIFT
addi sp, sp, -8
/* we now have a stack (whether we need one or not) */
moveli r40, hw2_last(hv_console_putc)
shl16insli r40, r40, hw1(hv_console_putc)
shl16insli r40, r40, hw0(hv_console_putc)
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 'r'
jalr r40
moveli r0, '_'
jalr r40
moveli r0, 'n'
jalr r40
moveli r0, '_'
jalr r40
moveli r0, 'k'
jalr r40
moveli r0, '\n'
jalr r40
#endif
/*
* Throughout this code r30 is pointer to the element of page
* list we are working on.
*
* Normally we get to the next element of the page list by
* incrementing r30 by eight. The exception is if the element
* on the page list is an IND_INDIRECTION in which case we use
* the element with the low bits masked off as the new value
* of r30.
*
* To get this started, we need the value passed to us (which
* will always be an IND_INDIRECTION) in memory somewhere with
* r30 pointing at it. To do that, we push the value passed
* to us on the stack and make r30 point to it.
*/
st sp, r30
move r30, sp
addi sp, sp, -16
#if CHIP_HAS_CBOX_HOME_MAP()
/*
* On TILE-GX, we need to flush all tiles' caches, since we may
* have been doing hash-for-home caching there. Note that we
* must do this _after_ we're completely done modifying any memory
* other than our output buffer (which we know is locally cached).
* We want the caches to be fully clean when we do the reexec,
* because the hypervisor is going to do this flush again at that
* point, and we don't want that second flush to overwrite any memory.
*/
{
move r0, zero /* cache_pa */
moveli r1, hw2_last(HV_FLUSH_EVICT_L2)
}
{
shl16insli r1, r1, hw1(HV_FLUSH_EVICT_L2)
movei r2, -1 /* cache_cpumask; -1 means all client tiles */
}
{
shl16insli r1, r1, hw0(HV_FLUSH_EVICT_L2) /* cache_control */
move r3, zero /* tlb_va */
}
{
move r4, zero /* tlb_length */
move r5, zero /* tlb_pgsize */
}
{
move r6, zero /* tlb_cpumask */
move r7, zero /* asids */
}
{
moveli r20, hw2_last(hv_flush_remote)
move r8, zero /* asidcount */
}
shl16insli r20, r20, hw1(hv_flush_remote)
shl16insli r20, r20, hw0(hv_flush_remote)
jalr r20
#endif
/* r33 is destination pointer, default to zero */
moveli r33, 0
.Lloop: ld r10, r30
andi r9, r10, 0xf /* low 4 bits tell us what type it is */
xor r10, r10, r9 /* r10 is now value with low 4 bits stripped */
cmpeqi r0, r9, 0x1 /* IND_DESTINATION */
beqzt r0, .Ltry2
move r33, r10
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 'd'
jalr r40
#endif
addi r30, r30, 8
j .Lloop
.Ltry2:
cmpeqi r0, r9, 0x2 /* IND_INDIRECTION */
beqzt r0, .Ltry4
move r30, r10
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 'i'
jalr r40
#endif
j .Lloop
.Ltry4:
cmpeqi r0, r9, 0x4 /* IND_DONE */
beqzt r0, .Ltry8
mf
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 'D'
jalr r40
moveli r0, '\n'
jalr r40
#endif
move r0, r32
moveli r41, hw2_last(hv_reexec)
shl16insli r41, r41, hw1(hv_reexec)
shl16insli r41, r41, hw0(hv_reexec)
jalr r41
/* we should not get here */
moveli r0, '?'
jalr r40
moveli r0, '\n'
jalr r40
j .Lhalt
.Ltry8: cmpeqi r0, r9, 0x8 /* IND_SOURCE */
beqz r0, .Lerr /* unknown type */
/* copy page at r10 to page at r33 */
move r11, r33
moveli r0, hw2_last(PAGE_SIZE)
shl16insli r0, r0, hw1(PAGE_SIZE)
shl16insli r0, r0, hw0(PAGE_SIZE)
add r33, r33, r0
/* copy word at r10 to word at r11 until r11 equals r33 */
/* We know page size must be multiple of 8, so we can unroll
* 8 times safely without any edge case checking.
*
* Issue a flush of the destination every 8 words to avoid
* incoherence when starting the new kernel. (Now this is
* just good paranoia because the hv_reexec call will also
* take care of this.)
*/
1:
{ ld r0, r10; addi r10, r10, 8 }
{ st r11, r0; addi r11, r11, 8 }
{ ld r0, r10; addi r10, r10, 8 }
{ st r11, r0; addi r11, r11, 8 }
{ ld r0, r10; addi r10, r10, 8 }
{ st r11, r0; addi r11, r11, 8 }
{ ld r0, r10; addi r10, r10, 8 }
{ st r11, r0; addi r11, r11, 8 }
{ ld r0, r10; addi r10, r10, 8 }
{ st r11, r0; addi r11, r11, 8 }
{ ld r0, r10; addi r10, r10, 8 }
{ st r11, r0; addi r11, r11, 8 }
{ ld r0, r10; addi r10, r10, 8 }
{ st r11, r0; addi r11, r11, 8 }
{ ld r0, r10; addi r10, r10, 8 }
{ st r11, r0 }
{ flush r11 ; addi r11, r11, 8 }
cmpeq r0, r33, r11
beqzt r0, 1b
#ifdef RELOCATE_NEW_KERNEL_VERBOSE
moveli r0, 's'
jalr r40
#endif
addi r30, r30, 8
j .Lloop
.Lerr: moveli r0, 'e'
jalr r40
moveli r0, 'r'
jalr r40
moveli r0, 'r'
jalr r40
moveli r0, '\n'
jalr r40
.Lhalt:
moveli r41, hw2_last(hv_halt)
shl16insli r41, r41, hw1(hv_halt)
shl16insli r41, r41, hw0(hv_halt)
jalr r41
STD_ENDPROC(relocate_new_kernel)
.section .rodata,"a"
.globl relocate_new_kernel_size
relocate_new_kernel_size:
.long .Lend_relocate_new_kernel - relocate_new_kernel

1625
arch/tile/kernel/setup.c Normal file

File diff suppressed because it is too large Load Diff

433
arch/tile/kernel/signal.c Normal file
View File

@@ -0,0 +1,433 @@
/*
* Copyright (C) 1991, 1992 Linus Torvalds
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/wait.h>
#include <linux/unistd.h>
#include <linux/stddef.h>
#include <linux/personality.h>
#include <linux/suspend.h>
#include <linux/ptrace.h>
#include <linux/elf.h>
#include <linux/compat.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <asm/processor.h>
#include <asm/ucontext.h>
#include <asm/sigframe.h>
#include <asm/syscalls.h>
#include <asm/vdso.h>
#include <arch/interrupts.h>
#define DEBUG_SIG 0
/*
* Do a signal return; undo the signal stack.
*/
int restore_sigcontext(struct pt_regs *regs,
struct sigcontext __user *sc)
{
int err = 0;
int i;
/* Always make any pending restarted system calls return -EINTR */
current_thread_info()->restart_block.fn = do_no_restart_syscall;
/*
* Enforce that sigcontext is like pt_regs, and doesn't mess
* up our stack alignment rules.
*/
BUILD_BUG_ON(sizeof(struct sigcontext) != sizeof(struct pt_regs));
BUILD_BUG_ON(sizeof(struct sigcontext) % 8 != 0);
for (i = 0; i < sizeof(struct pt_regs)/sizeof(long); ++i)
err |= __get_user(regs->regs[i], &sc->gregs[i]);
/* Ensure that the PL is always set to USER_PL. */
regs->ex1 = PL_ICS_EX1(USER_PL, EX1_ICS(regs->ex1));
regs->faultnum = INT_SWINT_1_SIGRETURN;
return err;
}
void signal_fault(const char *type, struct pt_regs *regs,
void __user *frame, int sig)
{
trace_unhandled_signal(type, regs, (unsigned long)frame, SIGSEGV);
force_sigsegv(sig, current);
}
/* The assembly shim for this function arranges to ignore the return value. */
SYSCALL_DEFINE0(rt_sigreturn)
{
struct pt_regs *regs = current_pt_regs();
struct rt_sigframe __user *frame =
(struct rt_sigframe __user *)(regs->sp);
sigset_t set;
if (!access_ok(VERIFY_READ, frame, sizeof(*frame)))
goto badframe;
if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set)))
goto badframe;
set_current_blocked(&set);
if (restore_sigcontext(regs, &frame->uc.uc_mcontext))
goto badframe;
if (restore_altstack(&frame->uc.uc_stack))
goto badframe;
return 0;
badframe:
signal_fault("bad sigreturn frame", regs, frame, 0);
return 0;
}
/*
* Set up a signal frame.
*/
int setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs)
{
int i, err = 0;
for (i = 0; i < sizeof(struct pt_regs)/sizeof(long); ++i)
err |= __put_user(regs->regs[i], &sc->gregs[i]);
return err;
}
/*
* Determine which stack to use..
*/
static inline void __user *get_sigframe(struct k_sigaction *ka,
struct pt_regs *regs,
size_t frame_size)
{
unsigned long sp;
/* Default to using normal stack */
sp = regs->sp;
/*
* If we are on the alternate signal stack and would overflow
* it, don't. Return an always-bogus address instead so we
* will die with SIGSEGV.
*/
if (on_sig_stack(sp) && !likely(on_sig_stack(sp - frame_size)))
return (void __user __force *)-1UL;
/* This is the X/Open sanctioned signal stack switching. */
if (ka->sa.sa_flags & SA_ONSTACK) {
if (sas_ss_flags(sp) == 0)
sp = current->sas_ss_sp + current->sas_ss_size;
}
sp -= frame_size;
/*
* Align the stack pointer according to the TILE ABI,
* i.e. so that on function entry (sp & 15) == 0.
*/
sp &= -16UL;
return (void __user *) sp;
}
static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
sigset_t *set, struct pt_regs *regs)
{
unsigned long restorer;
struct rt_sigframe __user *frame;
int err = 0;
int usig;
frame = get_sigframe(ka, regs, sizeof(*frame));
if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
goto give_sigsegv;
usig = current_thread_info()->exec_domain
&& current_thread_info()->exec_domain->signal_invmap
&& sig < 32
? current_thread_info()->exec_domain->signal_invmap[sig]
: sig;
/* Always write at least the signal number for the stack backtracer. */
if (ka->sa.sa_flags & SA_SIGINFO) {
/* At sigreturn time, restore the callee-save registers too. */
err |= copy_siginfo_to_user(&frame->info, info);
regs->flags |= PT_FLAGS_RESTORE_REGS;
} else {
err |= __put_user(info->si_signo, &frame->info.si_signo);
}
/* Create the ucontext. */
err |= __clear_user(&frame->save_area, sizeof(frame->save_area));
err |= __put_user(0, &frame->uc.uc_flags);
err |= __put_user(NULL, &frame->uc.uc_link);
err |= __save_altstack(&frame->uc.uc_stack, regs->sp);
err |= setup_sigcontext(&frame->uc.uc_mcontext, regs);
err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
if (err)
goto give_sigsegv;
restorer = VDSO_SYM(&__vdso_rt_sigreturn);
if (ka->sa.sa_flags & SA_RESTORER)
restorer = (unsigned long) ka->sa.sa_restorer;
/*
* Set up registers for signal handler.
* Registers that we don't modify keep the value they had from
* user-space at the time we took the signal.
* We always pass siginfo and mcontext, regardless of SA_SIGINFO,
* since some things rely on this (e.g. glibc's debug/segfault.c).
*/
regs->pc = (unsigned long) ka->sa.sa_handler;
regs->ex1 = PL_ICS_EX1(USER_PL, 1); /* set crit sec in handler */
regs->sp = (unsigned long) frame;
regs->lr = restorer;
regs->regs[0] = (unsigned long) usig;
regs->regs[1] = (unsigned long) &frame->info;
regs->regs[2] = (unsigned long) &frame->uc;
regs->flags |= PT_FLAGS_CALLER_SAVES;
return 0;
give_sigsegv:
signal_fault("bad setup frame", regs, frame, sig);
return -EFAULT;
}
/*
* OK, we're invoking a handler
*/
static void handle_signal(unsigned long sig, siginfo_t *info,
struct k_sigaction *ka,
struct pt_regs *regs)
{
sigset_t *oldset = sigmask_to_save();
int ret;
/* Are we from a system call? */
if (regs->faultnum == INT_SWINT_1) {
/* If so, check system call restarting.. */
switch (regs->regs[0]) {
case -ERESTART_RESTARTBLOCK:
case -ERESTARTNOHAND:
regs->regs[0] = -EINTR;
break;
case -ERESTARTSYS:
if (!(ka->sa.sa_flags & SA_RESTART)) {
regs->regs[0] = -EINTR;
break;
}
/* fallthrough */
case -ERESTARTNOINTR:
/* Reload caller-saves to restore r0..r5 and r10. */
regs->flags |= PT_FLAGS_CALLER_SAVES;
regs->regs[0] = regs->orig_r0;
regs->pc -= 8;
}
}
/* Set up the stack frame */
#ifdef CONFIG_COMPAT
if (is_compat_task())
ret = compat_setup_rt_frame(sig, ka, info, oldset, regs);
else
#endif
ret = setup_rt_frame(sig, ka, info, oldset, regs);
if (ret)
return;
signal_delivered(sig, info, ka, regs,
test_thread_flag(TIF_SINGLESTEP));
}
/*
* Note that 'init' is a special process: it doesn't get signals it doesn't
* want to handle. Thus you cannot kill init even with a SIGKILL even by
* mistake.
*/
void do_signal(struct pt_regs *regs)
{
siginfo_t info;
int signr;
struct k_sigaction ka;
/*
* i386 will check if we're coming from kernel mode and bail out
* here. In my experience this just turns weird crashes into
* weird spin-hangs. But if we find a case where this seems
* helpful, we can reinstate the check on "!user_mode(regs)".
*/
signr = get_signal_to_deliver(&info, &ka, regs, NULL);
if (signr > 0) {
/* Whee! Actually deliver the signal. */
handle_signal(signr, &info, &ka, regs);
goto done;
}
/* Did we come from a system call? */
if (regs->faultnum == INT_SWINT_1) {
/* Restart the system call - no handlers present */
switch (regs->regs[0]) {
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
regs->flags |= PT_FLAGS_CALLER_SAVES;
regs->regs[0] = regs->orig_r0;
regs->pc -= 8;
break;
case -ERESTART_RESTARTBLOCK:
regs->flags |= PT_FLAGS_CALLER_SAVES;
regs->regs[TREG_SYSCALL_NR] = __NR_restart_syscall;
regs->pc -= 8;
break;
}
}
/* If there's no signal to deliver, just put the saved sigmask back. */
restore_saved_sigmask();
done:
/* Avoid double syscall restart if there are nested signals. */
regs->faultnum = INT_SWINT_1_SIGRETURN;
}
int show_unhandled_signals = 1;
static int __init crashinfo(char *str)
{
unsigned long val;
const char *word;
if (*str == '\0')
val = 2;
else if (*str != '=' || strict_strtoul(++str, 0, &val) != 0)
return 0;
show_unhandled_signals = val;
switch (show_unhandled_signals) {
case 0:
word = "No";
break;
case 1:
word = "One-line";
break;
default:
word = "Detailed";
break;
}
pr_info("%s crash reports will be generated on the console\n", word);
return 1;
}
__setup("crashinfo", crashinfo);
static void dump_mem(void __user *address)
{
void __user *addr;
enum { region_size = 256, bytes_per_line = 16 };
int i, j, k;
int found_readable_mem = 0;
pr_err("\n");
if (!access_ok(VERIFY_READ, address, 1)) {
pr_err("Not dumping at address 0x%lx (kernel address)\n",
(unsigned long)address);
return;
}
addr = (void __user *)
(((unsigned long)address & -bytes_per_line) - region_size/2);
if (addr > address)
addr = NULL;
for (i = 0; i < region_size;
addr += bytes_per_line, i += bytes_per_line) {
unsigned char buf[bytes_per_line];
char line[100];
if (copy_from_user(buf, addr, bytes_per_line))
continue;
if (!found_readable_mem) {
pr_err("Dumping memory around address 0x%lx:\n",
(unsigned long)address);
found_readable_mem = 1;
}
j = sprintf(line, REGFMT":", (unsigned long)addr);
for (k = 0; k < bytes_per_line; ++k)
j += sprintf(&line[j], " %02x", buf[k]);
pr_err("%s\n", line);
}
if (!found_readable_mem)
pr_err("No readable memory around address 0x%lx\n",
(unsigned long)address);
}
void trace_unhandled_signal(const char *type, struct pt_regs *regs,
unsigned long address, int sig)
{
struct task_struct *tsk = current;
if (show_unhandled_signals == 0)
return;
/* If the signal is handled, don't show it here. */
if (!is_global_init(tsk)) {
void __user *handler =
tsk->sighand->action[sig-1].sa.sa_handler;
if (handler != SIG_IGN && handler != SIG_DFL)
return;
}
/* Rate-limit the one-line output, not the detailed output. */
if (show_unhandled_signals <= 1 && !printk_ratelimit())
return;
printk("%s%s[%d]: %s at %lx pc "REGFMT" signal %d",
task_pid_nr(tsk) > 1 ? KERN_INFO : KERN_EMERG,
tsk->comm, task_pid_nr(tsk), type, address, regs->pc, sig);
print_vma_addr(KERN_CONT " in ", regs->pc);
printk(KERN_CONT "\n");
if (show_unhandled_signals > 1) {
switch (sig) {
case SIGILL:
case SIGFPE:
case SIGSEGV:
case SIGBUS:
pr_err("User crash: signal %d,"
" trap %ld, address 0x%lx\n",
sig, regs->faultnum, address);
show_regs(regs);
dump_mem((void __user *)address);
break;
default:
pr_err("User crash: signal %d, trap %ld\n",
sig, regs->faultnum);
break;
}
}
}

View File

@@ -0,0 +1,776 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* A code-rewriter that enables instruction single-stepping.
* Derived from iLib's single-stepping code.
*/
#ifndef __tilegx__ /* Hardware support for single step unavailable. */
/* These functions are only used on the TILE platform */
#include <linux/slab.h>
#include <linux/thread_info.h>
#include <linux/uaccess.h>
#include <linux/mman.h>
#include <linux/types.h>
#include <linux/err.h>
#include <asm/cacheflush.h>
#include <asm/unaligned.h>
#include <arch/abi.h>
#include <arch/opcode.h>
#define signExtend17(val) sign_extend((val), 17)
#define TILE_X1_MASK (0xffffffffULL << 31)
int unaligned_printk;
static int __init setup_unaligned_printk(char *str)
{
long val;
if (strict_strtol(str, 0, &val) != 0)
return 0;
unaligned_printk = val;
pr_info("Printk for each unaligned data accesses is %s\n",
unaligned_printk ? "enabled" : "disabled");
return 1;
}
__setup("unaligned_printk=", setup_unaligned_printk);
unsigned int unaligned_fixup_count;
enum mem_op {
MEMOP_NONE,
MEMOP_LOAD,
MEMOP_STORE,
MEMOP_LOAD_POSTINCR,
MEMOP_STORE_POSTINCR
};
static inline tile_bundle_bits set_BrOff_X1(tile_bundle_bits n, s32 offset)
{
tile_bundle_bits result;
/* mask out the old offset */
tile_bundle_bits mask = create_BrOff_X1(-1);
result = n & (~mask);
/* or in the new offset */
result |= create_BrOff_X1(offset);
return result;
}
static inline tile_bundle_bits move_X1(tile_bundle_bits n, int dest, int src)
{
tile_bundle_bits result;
tile_bundle_bits op;
result = n & (~TILE_X1_MASK);
op = create_Opcode_X1(SPECIAL_0_OPCODE_X1) |
create_RRROpcodeExtension_X1(OR_SPECIAL_0_OPCODE_X1) |
create_Dest_X1(dest) |
create_SrcB_X1(TREG_ZERO) |
create_SrcA_X1(src) ;
result |= op;
return result;
}
static inline tile_bundle_bits nop_X1(tile_bundle_bits n)
{
return move_X1(n, TREG_ZERO, TREG_ZERO);
}
static inline tile_bundle_bits addi_X1(
tile_bundle_bits n, int dest, int src, int imm)
{
n &= ~TILE_X1_MASK;
n |= (create_SrcA_X1(src) |
create_Dest_X1(dest) |
create_Imm8_X1(imm) |
create_S_X1(0) |
create_Opcode_X1(IMM_0_OPCODE_X1) |
create_ImmOpcodeExtension_X1(ADDI_IMM_0_OPCODE_X1));
return n;
}
static tile_bundle_bits rewrite_load_store_unaligned(
struct single_step_state *state,
tile_bundle_bits bundle,
struct pt_regs *regs,
enum mem_op mem_op,
int size, int sign_ext)
{
unsigned char __user *addr;
int val_reg, addr_reg, err, val;
/* Get address and value registers */
if (bundle & TILEPRO_BUNDLE_Y_ENCODING_MASK) {
addr_reg = get_SrcA_Y2(bundle);
val_reg = get_SrcBDest_Y2(bundle);
} else if (mem_op == MEMOP_LOAD || mem_op == MEMOP_LOAD_POSTINCR) {
addr_reg = get_SrcA_X1(bundle);
val_reg = get_Dest_X1(bundle);
} else {
addr_reg = get_SrcA_X1(bundle);
val_reg = get_SrcB_X1(bundle);
}
/*
* If registers are not GPRs, don't try to handle it.
*
* FIXME: we could handle non-GPR loads by getting the real value
* from memory, writing it to the single step buffer, using a
* temp_reg to hold a pointer to that memory, then executing that
* instruction and resetting temp_reg. For non-GPR stores, it's a
* little trickier; we could use the single step buffer for that
* too, but we'd have to add some more state bits so that we could
* call back in here to copy that value to the real target. For
* now, we just handle the simple case.
*/
if ((val_reg >= PTREGS_NR_GPRS &&
(val_reg != TREG_ZERO ||
mem_op == MEMOP_LOAD ||
mem_op == MEMOP_LOAD_POSTINCR)) ||
addr_reg >= PTREGS_NR_GPRS)
return bundle;
/* If it's aligned, don't handle it specially */
addr = (void __user *)regs->regs[addr_reg];
if (((unsigned long)addr % size) == 0)
return bundle;
/*
* Return SIGBUS with the unaligned address, if requested.
* Note that we return SIGBUS even for completely invalid addresses
* as long as they are in fact unaligned; this matches what the
* tilepro hardware would be doing, if it could provide us with the
* actual bad address in an SPR, which it doesn't.
*/
if (unaligned_fixup == 0) {
siginfo_t info = {
.si_signo = SIGBUS,
.si_code = BUS_ADRALN,
.si_addr = addr
};
trace_unhandled_signal("unaligned trap", regs,
(unsigned long)addr, SIGBUS);
force_sig_info(info.si_signo, &info, current);
return (tilepro_bundle_bits) 0;
}
/* Handle unaligned load/store */
if (mem_op == MEMOP_LOAD || mem_op == MEMOP_LOAD_POSTINCR) {
unsigned short val_16;
switch (size) {
case 2:
err = copy_from_user(&val_16, addr, sizeof(val_16));
val = sign_ext ? ((short)val_16) : val_16;
break;
case 4:
err = copy_from_user(&val, addr, sizeof(val));
break;
default:
BUG();
}
if (err == 0) {
state->update_reg = val_reg;
state->update_value = val;
state->update = 1;
}
} else {
unsigned short val_16;
val = (val_reg == TREG_ZERO) ? 0 : regs->regs[val_reg];
switch (size) {
case 2:
val_16 = val;
err = copy_to_user(addr, &val_16, sizeof(val_16));
break;
case 4:
err = copy_to_user(addr, &val, sizeof(val));
break;
default:
BUG();
}
}
if (err) {
siginfo_t info = {
.si_signo = SIGSEGV,
.si_code = SEGV_MAPERR,
.si_addr = addr
};
trace_unhandled_signal("segfault", regs,
(unsigned long)addr, SIGSEGV);
force_sig_info(info.si_signo, &info, current);
return (tile_bundle_bits) 0;
}
if (unaligned_printk || unaligned_fixup_count == 0) {
pr_info("Process %d/%s: PC %#lx: Fixup of"
" unaligned %s at %#lx.\n",
current->pid, current->comm, regs->pc,
(mem_op == MEMOP_LOAD ||
mem_op == MEMOP_LOAD_POSTINCR) ?
"load" : "store",
(unsigned long)addr);
if (!unaligned_printk) {
#define P pr_info
P("\n");
P("Unaligned fixups in the kernel will slow your application considerably.\n");
P("To find them, write a \"1\" to /proc/sys/tile/unaligned_fixup/printk,\n");
P("which requests the kernel show all unaligned fixups, or write a \"0\"\n");
P("to /proc/sys/tile/unaligned_fixup/enabled, in which case each unaligned\n");
P("access will become a SIGBUS you can debug. No further warnings will be\n");
P("shown so as to avoid additional slowdown, but you can track the number\n");
P("of fixups performed via /proc/sys/tile/unaligned_fixup/count.\n");
P("Use the tile-addr2line command (see \"info addr2line\") to decode PCs.\n");
P("\n");
#undef P
}
}
++unaligned_fixup_count;
if (bundle & TILEPRO_BUNDLE_Y_ENCODING_MASK) {
/* Convert the Y2 instruction to a prefetch. */
bundle &= ~(create_SrcBDest_Y2(-1) |
create_Opcode_Y2(-1));
bundle |= (create_SrcBDest_Y2(TREG_ZERO) |
create_Opcode_Y2(LW_OPCODE_Y2));
/* Replace the load postincr with an addi */
} else if (mem_op == MEMOP_LOAD_POSTINCR) {
bundle = addi_X1(bundle, addr_reg, addr_reg,
get_Imm8_X1(bundle));
/* Replace the store postincr with an addi */
} else if (mem_op == MEMOP_STORE_POSTINCR) {
bundle = addi_X1(bundle, addr_reg, addr_reg,
get_Dest_Imm8_X1(bundle));
} else {
/* Convert the X1 instruction to a nop. */
bundle &= ~(create_Opcode_X1(-1) |
create_UnShOpcodeExtension_X1(-1) |
create_UnOpcodeExtension_X1(-1));
bundle |= (create_Opcode_X1(SHUN_0_OPCODE_X1) |
create_UnShOpcodeExtension_X1(
UN_0_SHUN_0_OPCODE_X1) |
create_UnOpcodeExtension_X1(
NOP_UN_0_SHUN_0_OPCODE_X1));
}
return bundle;
}
/*
* Called after execve() has started the new image. This allows us
* to reset the info state. Note that the the mmap'ed memory, if there
* was any, has already been unmapped by the exec.
*/
void single_step_execve(void)
{
struct thread_info *ti = current_thread_info();
kfree(ti->step_state);
ti->step_state = NULL;
}
/**
* single_step_once() - entry point when single stepping has been triggered.
* @regs: The machine register state
*
* When we arrive at this routine via a trampoline, the single step
* engine copies the executing bundle to the single step buffer.
* If the instruction is a condition branch, then the target is
* reset to one past the next instruction. If the instruction
* sets the lr, then that is noted. If the instruction is a jump
* or call, then the new target pc is preserved and the current
* bundle instruction set to null.
*
* The necessary post-single-step rewriting information is stored in
* single_step_state-> We use data segment values because the
* stack will be rewound when we run the rewritten single-stepped
* instruction.
*/
void single_step_once(struct pt_regs *regs)
{
extern tile_bundle_bits __single_step_ill_insn;
extern tile_bundle_bits __single_step_j_insn;
extern tile_bundle_bits __single_step_addli_insn;
extern tile_bundle_bits __single_step_auli_insn;
struct thread_info *info = (void *)current_thread_info();
struct single_step_state *state = info->step_state;
int is_single_step = test_ti_thread_flag(info, TIF_SINGLESTEP);
tile_bundle_bits __user *buffer, *pc;
tile_bundle_bits bundle;
int temp_reg;
int target_reg = TREG_LR;
int err;
enum mem_op mem_op = MEMOP_NONE;
int size = 0, sign_ext = 0; /* happy compiler */
asm(
" .pushsection .rodata.single_step\n"
" .align 8\n"
" .globl __single_step_ill_insn\n"
"__single_step_ill_insn:\n"
" ill\n"
" .globl __single_step_addli_insn\n"
"__single_step_addli_insn:\n"
" { nop; addli r0, zero, 0 }\n"
" .globl __single_step_auli_insn\n"
"__single_step_auli_insn:\n"
" { nop; auli r0, r0, 0 }\n"
" .globl __single_step_j_insn\n"
"__single_step_j_insn:\n"
" j .\n"
" .popsection\n"
);
/*
* Enable interrupts here to allow touching userspace and the like.
* The callers expect this: do_trap() already has interrupts
* enabled, and do_work_pending() handles functions that enable
* interrupts internally.
*/
local_irq_enable();
if (state == NULL) {
/* allocate a page of writable, executable memory */
state = kmalloc(sizeof(struct single_step_state), GFP_KERNEL);
if (state == NULL) {
pr_err("Out of kernel memory trying to single-step\n");
return;
}
/* allocate a cache line of writable, executable memory */
buffer = (void __user *) vm_mmap(NULL, 0, 64,
PROT_EXEC | PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
0);
if (IS_ERR((void __force *)buffer)) {
kfree(state);
pr_err("Out of kernel pages trying to single-step\n");
return;
}
state->buffer = buffer;
state->is_enabled = 0;
info->step_state = state;
/* Validate our stored instruction patterns */
BUG_ON(get_Opcode_X1(__single_step_addli_insn) !=
ADDLI_OPCODE_X1);
BUG_ON(get_Opcode_X1(__single_step_auli_insn) !=
AULI_OPCODE_X1);
BUG_ON(get_SrcA_X1(__single_step_addli_insn) != TREG_ZERO);
BUG_ON(get_Dest_X1(__single_step_addli_insn) != 0);
BUG_ON(get_JOffLong_X1(__single_step_j_insn) != 0);
}
/*
* If we are returning from a syscall, we still haven't hit the
* "ill" for the swint1 instruction. So back the PC up to be
* pointing at the swint1, but we'll actually return directly
* back to the "ill" so we come back in via SIGILL as if we
* had "executed" the swint1 without ever being in kernel space.
*/
if (regs->faultnum == INT_SWINT_1)
regs->pc -= 8;
pc = (tile_bundle_bits __user *)(regs->pc);
if (get_user(bundle, pc) != 0) {
pr_err("Couldn't read instruction at %p trying to step\n", pc);
return;
}
/* We'll follow the instruction with 2 ill op bundles */
state->orig_pc = (unsigned long)pc;
state->next_pc = (unsigned long)(pc + 1);
state->branch_next_pc = 0;
state->update = 0;
if (!(bundle & TILEPRO_BUNDLE_Y_ENCODING_MASK)) {
/* two wide, check for control flow */
int opcode = get_Opcode_X1(bundle);
switch (opcode) {
/* branches */
case BRANCH_OPCODE_X1:
{
s32 offset = signExtend17(get_BrOff_X1(bundle));
/*
* For branches, we use a rewriting trick to let the
* hardware evaluate whether the branch is taken or
* untaken. We record the target offset and then
* rewrite the branch instruction to target 1 insn
* ahead if the branch is taken. We then follow the
* rewritten branch with two bundles, each containing
* an "ill" instruction. The supervisor examines the
* pc after the single step code is executed, and if
* the pc is the first ill instruction, then the
* branch (if any) was not taken. If the pc is the
* second ill instruction, then the branch was
* taken. The new pc is computed for these cases, and
* inserted into the registers for the thread. If
* the pc is the start of the single step code, then
* an exception or interrupt was taken before the
* code started processing, and the same "original"
* pc is restored. This change, different from the
* original implementation, has the advantage of
* executing a single user instruction.
*/
state->branch_next_pc = (unsigned long)(pc + offset);
/* rewrite branch offset to go forward one bundle */
bundle = set_BrOff_X1(bundle, 2);
}
break;
/* jumps */
case JALB_OPCODE_X1:
case JALF_OPCODE_X1:
state->update = 1;
state->next_pc =
(unsigned long) (pc + get_JOffLong_X1(bundle));
break;
case JB_OPCODE_X1:
case JF_OPCODE_X1:
state->next_pc =
(unsigned long) (pc + get_JOffLong_X1(bundle));
bundle = nop_X1(bundle);
break;
case SPECIAL_0_OPCODE_X1:
switch (get_RRROpcodeExtension_X1(bundle)) {
/* jump-register */
case JALRP_SPECIAL_0_OPCODE_X1:
case JALR_SPECIAL_0_OPCODE_X1:
state->update = 1;
state->next_pc =
regs->regs[get_SrcA_X1(bundle)];
break;
case JRP_SPECIAL_0_OPCODE_X1:
case JR_SPECIAL_0_OPCODE_X1:
state->next_pc =
regs->regs[get_SrcA_X1(bundle)];
bundle = nop_X1(bundle);
break;
case LNK_SPECIAL_0_OPCODE_X1:
state->update = 1;
target_reg = get_Dest_X1(bundle);
break;
/* stores */
case SH_SPECIAL_0_OPCODE_X1:
mem_op = MEMOP_STORE;
size = 2;
break;
case SW_SPECIAL_0_OPCODE_X1:
mem_op = MEMOP_STORE;
size = 4;
break;
}
break;
/* loads and iret */
case SHUN_0_OPCODE_X1:
if (get_UnShOpcodeExtension_X1(bundle) ==
UN_0_SHUN_0_OPCODE_X1) {
switch (get_UnOpcodeExtension_X1(bundle)) {
case LH_UN_0_SHUN_0_OPCODE_X1:
mem_op = MEMOP_LOAD;
size = 2;
sign_ext = 1;
break;
case LH_U_UN_0_SHUN_0_OPCODE_X1:
mem_op = MEMOP_LOAD;
size = 2;
sign_ext = 0;
break;
case LW_UN_0_SHUN_0_OPCODE_X1:
mem_op = MEMOP_LOAD;
size = 4;
break;
case IRET_UN_0_SHUN_0_OPCODE_X1:
{
unsigned long ex0_0 = __insn_mfspr(
SPR_EX_CONTEXT_0_0);
unsigned long ex0_1 = __insn_mfspr(
SPR_EX_CONTEXT_0_1);
/*
* Special-case it if we're iret'ing
* to PL0 again. Otherwise just let
* it run and it will generate SIGILL.
*/
if (EX1_PL(ex0_1) == USER_PL) {
state->next_pc = ex0_0;
regs->ex1 = ex0_1;
bundle = nop_X1(bundle);
}
}
}
}
break;
#if CHIP_HAS_WH64()
/* postincrement operations */
case IMM_0_OPCODE_X1:
switch (get_ImmOpcodeExtension_X1(bundle)) {
case LWADD_IMM_0_OPCODE_X1:
mem_op = MEMOP_LOAD_POSTINCR;
size = 4;
break;
case LHADD_IMM_0_OPCODE_X1:
mem_op = MEMOP_LOAD_POSTINCR;
size = 2;
sign_ext = 1;
break;
case LHADD_U_IMM_0_OPCODE_X1:
mem_op = MEMOP_LOAD_POSTINCR;
size = 2;
sign_ext = 0;
break;
case SWADD_IMM_0_OPCODE_X1:
mem_op = MEMOP_STORE_POSTINCR;
size = 4;
break;
case SHADD_IMM_0_OPCODE_X1:
mem_op = MEMOP_STORE_POSTINCR;
size = 2;
break;
default:
break;
}
break;
#endif /* CHIP_HAS_WH64() */
}
if (state->update) {
/*
* Get an available register. We start with a
* bitmask with 1's for available registers.
* We truncate to the low 32 registers since
* we are guaranteed to have set bits in the
* low 32 bits, then use ctz to pick the first.
*/
u32 mask = (u32) ~((1ULL << get_Dest_X0(bundle)) |
(1ULL << get_SrcA_X0(bundle)) |
(1ULL << get_SrcB_X0(bundle)) |
(1ULL << target_reg));
temp_reg = __builtin_ctz(mask);
state->update_reg = temp_reg;
state->update_value = regs->regs[temp_reg];
regs->regs[temp_reg] = (unsigned long) (pc+1);
regs->flags |= PT_FLAGS_RESTORE_REGS;
bundle = move_X1(bundle, target_reg, temp_reg);
}
} else {
int opcode = get_Opcode_Y2(bundle);
switch (opcode) {
/* loads */
case LH_OPCODE_Y2:
mem_op = MEMOP_LOAD;
size = 2;
sign_ext = 1;
break;
case LH_U_OPCODE_Y2:
mem_op = MEMOP_LOAD;
size = 2;
sign_ext = 0;
break;
case LW_OPCODE_Y2:
mem_op = MEMOP_LOAD;
size = 4;
break;
/* stores */
case SH_OPCODE_Y2:
mem_op = MEMOP_STORE;
size = 2;
break;
case SW_OPCODE_Y2:
mem_op = MEMOP_STORE;
size = 4;
break;
}
}
/*
* Check if we need to rewrite an unaligned load/store.
* Returning zero is a special value meaning we need to SIGSEGV.
*/
if (mem_op != MEMOP_NONE && unaligned_fixup >= 0) {
bundle = rewrite_load_store_unaligned(state, bundle, regs,
mem_op, size, sign_ext);
if (bundle == 0)
return;
}
/* write the bundle to our execution area */
buffer = state->buffer;
err = __put_user(bundle, buffer++);
/*
* If we're really single-stepping, we take an INT_ILL after.
* If we're just handling an unaligned access, we can just
* jump directly back to where we were in user code.
*/
if (is_single_step) {
err |= __put_user(__single_step_ill_insn, buffer++);
err |= __put_user(__single_step_ill_insn, buffer++);
} else {
long delta;
if (state->update) {
/* We have some state to update; do it inline */
int ha16;
bundle = __single_step_addli_insn;
bundle |= create_Dest_X1(state->update_reg);
bundle |= create_Imm16_X1(state->update_value);
err |= __put_user(bundle, buffer++);
bundle = __single_step_auli_insn;
bundle |= create_Dest_X1(state->update_reg);
bundle |= create_SrcA_X1(state->update_reg);
ha16 = (state->update_value + 0x8000) >> 16;
bundle |= create_Imm16_X1(ha16);
err |= __put_user(bundle, buffer++);
state->update = 0;
}
/* End with a jump back to the next instruction */
delta = ((regs->pc + TILE_BUNDLE_SIZE_IN_BYTES) -
(unsigned long)buffer) >>
TILE_LOG2_BUNDLE_ALIGNMENT_IN_BYTES;
bundle = __single_step_j_insn;
bundle |= create_JOffLong_X1(delta);
err |= __put_user(bundle, buffer++);
}
if (err) {
pr_err("Fault when writing to single-step buffer\n");
return;
}
/*
* Flush the buffer.
* We do a local flush only, since this is a thread-specific buffer.
*/
__flush_icache_range((unsigned long)state->buffer,
(unsigned long)buffer);
/* Indicate enabled */
state->is_enabled = is_single_step;
regs->pc = (unsigned long)state->buffer;
/* Fault immediately if we are coming back from a syscall. */
if (regs->faultnum == INT_SWINT_1)
regs->pc += 8;
}
#else
#include <linux/smp.h>
#include <linux/ptrace.h>
#include <arch/spr_def.h>
static DEFINE_PER_CPU(unsigned long, ss_saved_pc);
/*
* Called directly on the occasion of an interrupt.
*
* If the process doesn't have single step set, then we use this as an
* opportunity to turn single step off.
*
* It has been mentioned that we could conditionally turn off single stepping
* on each entry into the kernel and rely on single_step_once to turn it
* on for the processes that matter (as we already do), but this
* implementation is somewhat more efficient in that we muck with registers
* once on a bum interrupt rather than on every entry into the kernel.
*
* If SINGLE_STEP_CONTROL_K has CANCELED set, then an interrupt occurred,
* so we have to run through this process again before we can say that an
* instruction has executed.
*
* swint will set CANCELED, but it's a legitimate instruction. Fortunately
* it changes the PC. If it hasn't changed, then we know that the interrupt
* wasn't generated by swint and we'll need to run this process again before
* we can say an instruction has executed.
*
* If either CANCELED == 0 or the PC's changed, we send out SIGTRAPs and get
* on with our lives.
*/
void gx_singlestep_handle(struct pt_regs *regs, int fault_num)
{
unsigned long *ss_pc = &__get_cpu_var(ss_saved_pc);
struct thread_info *info = (void *)current_thread_info();
int is_single_step = test_ti_thread_flag(info, TIF_SINGLESTEP);
unsigned long control = __insn_mfspr(SPR_SINGLE_STEP_CONTROL_K);
if (is_single_step == 0) {
__insn_mtspr(SPR_SINGLE_STEP_EN_K_K, 0);
} else if ((*ss_pc != regs->pc) ||
(!(control & SPR_SINGLE_STEP_CONTROL_1__CANCELED_MASK))) {
ptrace_notify(SIGTRAP);
control |= SPR_SINGLE_STEP_CONTROL_1__CANCELED_MASK;
control |= SPR_SINGLE_STEP_CONTROL_1__INHIBIT_MASK;
__insn_mtspr(SPR_SINGLE_STEP_CONTROL_K, control);
}
}
/*
* Called from need_singlestep. Set up the control registers and the enable
* register, then return back.
*/
void single_step_once(struct pt_regs *regs)
{
unsigned long *ss_pc = &__get_cpu_var(ss_saved_pc);
unsigned long control = __insn_mfspr(SPR_SINGLE_STEP_CONTROL_K);
*ss_pc = regs->pc;
control |= SPR_SINGLE_STEP_CONTROL_1__CANCELED_MASK;
control |= SPR_SINGLE_STEP_CONTROL_1__INHIBIT_MASK;
__insn_mtspr(SPR_SINGLE_STEP_CONTROL_K, control);
__insn_mtspr(SPR_SINGLE_STEP_EN_K_K, 1 << USER_PL);
}
void single_step_execve(void)
{
/* Nothing */
}
#endif /* !__tilegx__ */

244
arch/tile/kernel/smp.c Normal file
View File

@@ -0,0 +1,244 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* TILE SMP support routines.
*/
#include <linux/smp.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <asm/cacheflush.h>
HV_Topology smp_topology __write_once;
EXPORT_SYMBOL(smp_topology);
#if CHIP_HAS_IPI()
static unsigned long __iomem *ipi_mappings[NR_CPUS];
#endif
/*
* Top-level send_IPI*() functions to send messages to other cpus.
*/
/* Set by smp_send_stop() to avoid recursive panics. */
static int stopping_cpus;
static void __send_IPI_many(HV_Recipient *recip, int nrecip, int tag)
{
int sent = 0;
while (sent < nrecip) {
int rc = hv_send_message(recip, nrecip,
(HV_VirtAddr)&tag, sizeof(tag));
if (rc < 0) {
if (!stopping_cpus) /* avoid recursive panic */
panic("hv_send_message returned %d", rc);
break;
}
WARN_ONCE(rc == 0, "hv_send_message() returned zero\n");
sent += rc;
}
}
void send_IPI_single(int cpu, int tag)
{
HV_Recipient recip = {
.y = cpu / smp_width,
.x = cpu % smp_width,
.state = HV_TO_BE_SENT
};
__send_IPI_many(&recip, 1, tag);
}
void send_IPI_many(const struct cpumask *mask, int tag)
{
HV_Recipient recip[NR_CPUS];
int cpu;
int nrecip = 0;
int my_cpu = smp_processor_id();
for_each_cpu(cpu, mask) {
HV_Recipient *r;
BUG_ON(cpu == my_cpu);
r = &recip[nrecip++];
r->y = cpu / smp_width;
r->x = cpu % smp_width;
r->state = HV_TO_BE_SENT;
}
__send_IPI_many(recip, nrecip, tag);
}
void send_IPI_allbutself(int tag)
{
struct cpumask mask;
cpumask_copy(&mask, cpu_online_mask);
cpumask_clear_cpu(smp_processor_id(), &mask);
send_IPI_many(&mask, tag);
}
/*
* Functions related to starting/stopping cpus.
*/
/* Handler to start the current cpu. */
static void smp_start_cpu_interrupt(void)
{
get_irq_regs()->pc = start_cpu_function_addr;
}
/* Handler to stop the current cpu. */
static void smp_stop_cpu_interrupt(void)
{
set_cpu_online(smp_processor_id(), 0);
arch_local_irq_disable_all();
for (;;)
asm("nap; nop");
}
/* This function calls the 'stop' function on all other CPUs in the system. */
void smp_send_stop(void)
{
stopping_cpus = 1;
send_IPI_allbutself(MSG_TAG_STOP_CPU);
}
/* On panic, just wait; we may get an smp_send_stop() later on. */
void panic_smp_self_stop(void)
{
while (1)
asm("nap; nop");
}
/*
* Dispatch code called from hv_message_intr() for HV_MSG_TILE hv messages.
*/
void evaluate_message(int tag)
{
switch (tag) {
case MSG_TAG_START_CPU: /* Start up a cpu */
smp_start_cpu_interrupt();
break;
case MSG_TAG_STOP_CPU: /* Sent to shut down slave CPU's */
smp_stop_cpu_interrupt();
break;
case MSG_TAG_CALL_FUNCTION_MANY: /* Call function on cpumask */
generic_smp_call_function_interrupt();
break;
case MSG_TAG_CALL_FUNCTION_SINGLE: /* Call function on one other CPU */
generic_smp_call_function_single_interrupt();
break;
default:
panic("Unknown IPI message tag %d", tag);
break;
}
}
/*
* flush_icache_range() code uses smp_call_function().
*/
struct ipi_flush {
unsigned long start;
unsigned long end;
};
static void ipi_flush_icache_range(void *info)
{
struct ipi_flush *flush = (struct ipi_flush *) info;
__flush_icache_range(flush->start, flush->end);
}
void flush_icache_range(unsigned long start, unsigned long end)
{
struct ipi_flush flush = { start, end };
preempt_disable();
on_each_cpu(ipi_flush_icache_range, &flush, 1);
preempt_enable();
}
/* Called when smp_send_reschedule() triggers IRQ_RESCHEDULE. */
static irqreturn_t handle_reschedule_ipi(int irq, void *token)
{
__get_cpu_var(irq_stat).irq_resched_count++;
scheduler_ipi();
return IRQ_HANDLED;
}
static struct irqaction resched_action = {
.handler = handle_reschedule_ipi,
.name = "resched",
.dev_id = handle_reschedule_ipi /* unique token */,
};
void __init ipi_init(void)
{
#if CHIP_HAS_IPI()
int cpu;
/* Map IPI trigger MMIO addresses. */
for_each_possible_cpu(cpu) {
HV_Coord tile;
HV_PTE pte;
unsigned long offset;
tile.x = cpu_x(cpu);
tile.y = cpu_y(cpu);
if (hv_get_ipi_pte(tile, KERNEL_PL, &pte) != 0)
panic("Failed to initialize IPI for cpu %d\n", cpu);
offset = PFN_PHYS(pte_pfn(pte));
ipi_mappings[cpu] = ioremap_prot(offset, PAGE_SIZE, pte);
}
#endif
/* Bind handle_reschedule_ipi() to IRQ_RESCHEDULE. */
tile_irq_activate(IRQ_RESCHEDULE, TILE_IRQ_PERCPU);
BUG_ON(setup_irq(IRQ_RESCHEDULE, &resched_action));
}
#if CHIP_HAS_IPI()
void smp_send_reschedule(int cpu)
{
WARN_ON(cpu_is_offline(cpu));
/*
* We just want to do an MMIO store. The traditional writeq()
* functions aren't really correct here, since they're always
* directed at the PCI shim. For now, just do a raw store,
* casting away the __iomem attribute.
*/
((unsigned long __force *)ipi_mappings[cpu])[IRQ_RESCHEDULE] = 0;
}
#else
void smp_send_reschedule(int cpu)
{
HV_Coord coord;
WARN_ON(cpu_is_offline(cpu));
coord.y = cpu_y(cpu);
coord.x = cpu_x(cpu);
hv_trigger_ipi(coord, IRQ_RESCHEDULE);
}
#endif /* CHIP_HAS_IPI() */

267
arch/tile/kernel/smpboot.c Normal file
View File

@@ -0,0 +1,267 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/kernel_stat.h>
#include <linux/bootmem.h>
#include <linux/notifier.h>
#include <linux/cpu.h>
#include <linux/percpu.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <asm/mmu_context.h>
#include <asm/tlbflush.h>
#include <asm/sections.h>
/* State of each CPU. */
static DEFINE_PER_CPU(int, cpu_state) = { 0 };
/* The messaging code jumps to this pointer during boot-up */
unsigned long start_cpu_function_addr;
/* Called very early during startup to mark boot cpu as online */
void __init smp_prepare_boot_cpu(void)
{
int cpu = smp_processor_id();
set_cpu_online(cpu, 1);
set_cpu_present(cpu, 1);
__get_cpu_var(cpu_state) = CPU_ONLINE;
init_messaging();
}
static void start_secondary(void);
/*
* Called at the top of init() to launch all the other CPUs.
* They run free to complete their initialization and then wait
* until they get an IPI from the boot cpu to come online.
*/
void __init smp_prepare_cpus(unsigned int max_cpus)
{
long rc;
int cpu, cpu_count;
int boot_cpu = smp_processor_id();
current_thread_info()->cpu = boot_cpu;
/*
* Pin this task to the boot CPU while we bring up the others,
* just to make sure we don't uselessly migrate as they come up.
*/
rc = sched_setaffinity(current->pid, cpumask_of(boot_cpu));
if (rc != 0)
pr_err("Couldn't set init affinity to boot cpu (%ld)\n", rc);
/* Print information about disabled and dataplane cpus. */
print_disabled_cpus();
/*
* Tell the messaging subsystem how to respond to the
* startup message. We use a level of indirection to avoid
* confusing the linker with the fact that the messaging
* subsystem is calling __init code.
*/
start_cpu_function_addr = (unsigned long) &online_secondary;
/* Set up thread context for all new processors. */
cpu_count = 1;
for (cpu = 0; cpu < NR_CPUS; ++cpu) {
struct task_struct *idle;
if (cpu == boot_cpu)
continue;
if (!cpu_possible(cpu)) {
/*
* Make this processor do nothing on boot.
* Note that we don't give the boot_pc function
* a stack, so it has to be assembly code.
*/
per_cpu(boot_sp, cpu) = 0;
per_cpu(boot_pc, cpu) = (unsigned long) smp_nap;
continue;
}
/* Create a new idle thread to run start_secondary() */
idle = fork_idle(cpu);
if (IS_ERR(idle))
panic("failed fork for CPU %d", cpu);
idle->thread.pc = (unsigned long) start_secondary;
/* Make this thread the boot thread for this processor */
per_cpu(boot_sp, cpu) = task_ksp0(idle);
per_cpu(boot_pc, cpu) = idle->thread.pc;
++cpu_count;
}
BUG_ON(cpu_count > (max_cpus ? max_cpus : 1));
/* Fire up the other tiles, if any */
init_cpu_present(cpu_possible_mask);
if (cpumask_weight(cpu_present_mask) > 1) {
mb(); /* make sure all data is visible to new processors */
hv_start_all_tiles();
}
}
static __initdata struct cpumask init_affinity;
static __init int reset_init_affinity(void)
{
long rc = sched_setaffinity(current->pid, &init_affinity);
if (rc != 0)
pr_warning("couldn't reset init affinity (%ld)\n",
rc);
return 0;
}
late_initcall(reset_init_affinity);
static struct cpumask cpu_started __cpuinitdata;
/*
* Activate a secondary processor. Very minimal; don't add anything
* to this path without knowing what you're doing, since SMP booting
* is pretty fragile.
*/
static void __cpuinit start_secondary(void)
{
int cpuid = smp_processor_id();
/* Set our thread pointer appropriately. */
set_my_cpu_offset(__per_cpu_offset[cpuid]);
preempt_disable();
/*
* In large machines even this will slow us down, since we
* will be contending for for the printk spinlock.
*/
/* printk(KERN_DEBUG "Initializing CPU#%d\n", cpuid); */
/* Initialize the current asid for our first page table. */
__get_cpu_var(current_asid) = min_asid;
/* Set up this thread as another owner of the init_mm */
atomic_inc(&init_mm.mm_count);
current->active_mm = &init_mm;
if (current->mm)
BUG();
enter_lazy_tlb(&init_mm, current);
/* Allow hypervisor messages to be received */
init_messaging();
local_irq_enable();
/* Indicate that we're ready to come up. */
/* Must not do this before we're ready to receive messages */
if (cpumask_test_and_set_cpu(cpuid, &cpu_started)) {
pr_warning("CPU#%d already started!\n", cpuid);
for (;;)
local_irq_enable();
}
smp_nap();
}
/*
* Bring a secondary processor online.
*/
void __cpuinit online_secondary(void)
{
/*
* low-memory mappings have been cleared, flush them from
* the local TLBs too.
*/
local_flush_tlb();
BUG_ON(in_interrupt());
/* This must be done before setting cpu_online_mask */
wmb();
notify_cpu_starting(smp_processor_id());
set_cpu_online(smp_processor_id(), 1);
__get_cpu_var(cpu_state) = CPU_ONLINE;
/* Set up tile-specific state for this cpu. */
setup_cpu(0);
/* Set up tile-timer clock-event device on this cpu */
setup_tile_timer();
cpu_startup_entry(CPUHP_ONLINE);
}
int __cpuinit __cpu_up(unsigned int cpu, struct task_struct *tidle)
{
/* Wait 5s total for all CPUs for them to come online */
static int timeout;
for (; !cpumask_test_cpu(cpu, &cpu_started); timeout++) {
if (timeout >= 50000) {
pr_info("skipping unresponsive cpu%d\n", cpu);
local_irq_enable();
return -EIO;
}
udelay(100);
}
local_irq_enable();
per_cpu(cpu_state, cpu) = CPU_UP_PREPARE;
/* Unleash the CPU! */
send_IPI_single(cpu, MSG_TAG_START_CPU);
while (!cpumask_test_cpu(cpu, cpu_online_mask))
cpu_relax();
return 0;
}
static void panic_start_cpu(void)
{
panic("Received a MSG_START_CPU IPI after boot finished.");
}
void __init smp_cpus_done(unsigned int max_cpus)
{
int cpu, next, rc;
/* Reset the response to a (now illegal) MSG_START_CPU IPI. */
start_cpu_function_addr = (unsigned long) &panic_start_cpu;
cpumask_copy(&init_affinity, cpu_online_mask);
/*
* Pin ourselves to a single cpu in the initial affinity set
* so that kernel mappings for the rootfs are not in the dataplane,
* if set, and to avoid unnecessary migrating during bringup.
* Use the last cpu just in case the whole chip has been
* isolated from the scheduler, to keep init away from likely
* more useful user code. This also ensures that work scheduled
* via schedule_delayed_work() in the init routines will land
* on this cpu.
*/
for (cpu = cpumask_first(&init_affinity);
(next = cpumask_next(cpu, &init_affinity)) < nr_cpu_ids;
cpu = next)
;
rc = sched_setaffinity(current->pid, cpumask_of(cpu));
if (rc != 0)
pr_err("Couldn't set init affinity to cpu %d (%d)\n", cpu, rc);
}

493
arch/tile/kernel/stack.c Normal file
View File

@@ -0,0 +1,493 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/pfn.h>
#include <linux/kallsyms.h>
#include <linux/stacktrace.h>
#include <linux/uaccess.h>
#include <linux/mmzone.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <asm/backtrace.h>
#include <asm/page.h>
#include <asm/ucontext.h>
#include <asm/switch_to.h>
#include <asm/sigframe.h>
#include <asm/stack.h>
#include <asm/vdso.h>
#include <arch/abi.h>
#include <arch/interrupts.h>
#define KBT_ONGOING 0 /* Backtrace still ongoing */
#define KBT_DONE 1 /* Backtrace cleanly completed */
#define KBT_RUNNING 2 /* Can't run backtrace on a running task */
#define KBT_LOOP 3 /* Backtrace entered a loop */
/* Is address on the specified kernel stack? */
static int in_kernel_stack(struct KBacktraceIterator *kbt, unsigned long sp)
{
ulong kstack_base = (ulong) kbt->task->stack;
if (kstack_base == 0) /* corrupt task pointer; just follow stack... */
return sp >= PAGE_OFFSET && sp < (unsigned long)high_memory;
return sp >= kstack_base && sp < kstack_base + THREAD_SIZE;
}
/* Callback for backtracer; basically a glorified memcpy */
static bool read_memory_func(void *result, unsigned long address,
unsigned int size, void *vkbt)
{
int retval;
struct KBacktraceIterator *kbt = (struct KBacktraceIterator *)vkbt;
if (address == 0)
return 0;
if (__kernel_text_address(address)) {
/* OK to read kernel code. */
} else if (address >= PAGE_OFFSET) {
/* We only tolerate kernel-space reads of this task's stack */
if (!in_kernel_stack(kbt, address))
return 0;
} else if (!kbt->is_current) {
return 0; /* can't read from other user address spaces */
}
pagefault_disable();
retval = __copy_from_user_inatomic(result,
(void __user __force *)address,
size);
pagefault_enable();
return (retval == 0);
}
/* Return a pt_regs pointer for a valid fault handler frame */
static struct pt_regs *valid_fault_handler(struct KBacktraceIterator* kbt)
{
const char *fault = NULL; /* happy compiler */
char fault_buf[64];
unsigned long sp = kbt->it.sp;
struct pt_regs *p;
if (sp % sizeof(long) != 0)
return NULL;
if (!in_kernel_stack(kbt, sp))
return NULL;
if (!in_kernel_stack(kbt, sp + C_ABI_SAVE_AREA_SIZE + PTREGS_SIZE-1))
return NULL;
p = (struct pt_regs *)(sp + C_ABI_SAVE_AREA_SIZE);
if (p->faultnum == INT_SWINT_1 || p->faultnum == INT_SWINT_1_SIGRETURN)
fault = "syscall";
else {
if (kbt->verbose) { /* else we aren't going to use it */
snprintf(fault_buf, sizeof(fault_buf),
"interrupt %ld", p->faultnum);
fault = fault_buf;
}
}
if (EX1_PL(p->ex1) == KERNEL_PL &&
__kernel_text_address(p->pc) &&
in_kernel_stack(kbt, p->sp) &&
p->sp >= sp) {
if (kbt->verbose)
pr_err(" <%s while in kernel mode>\n", fault);
} else if (EX1_PL(p->ex1) == USER_PL &&
p->pc < PAGE_OFFSET &&
p->sp < PAGE_OFFSET) {
if (kbt->verbose)
pr_err(" <%s while in user mode>\n", fault);
} else if (kbt->verbose) {
pr_err(" (odd fault: pc %#lx, sp %#lx, ex1 %#lx?)\n",
p->pc, p->sp, p->ex1);
p = NULL;
}
if (!kbt->profile || ((1ULL << p->faultnum) & QUEUED_INTERRUPTS) == 0)
return p;
return NULL;
}
/* Is the pc pointing to a sigreturn trampoline? */
static int is_sigreturn(unsigned long pc)
{
return current->mm && (pc == VDSO_SYM(&__vdso_rt_sigreturn));
}
/* Return a pt_regs pointer for a valid signal handler frame */
static struct pt_regs *valid_sigframe(struct KBacktraceIterator* kbt,
struct rt_sigframe* kframe)
{
BacktraceIterator *b = &kbt->it;
if (is_sigreturn(b->pc) && b->sp < PAGE_OFFSET &&
b->sp % sizeof(long) == 0) {
int retval;
pagefault_disable();
retval = __copy_from_user_inatomic(
kframe, (void __user __force *)b->sp,
sizeof(*kframe));
pagefault_enable();
if (retval != 0 ||
(unsigned int)(kframe->info.si_signo) >= _NSIG)
return NULL;
if (kbt->verbose) {
pr_err(" <received signal %d>\n",
kframe->info.si_signo);
}
return (struct pt_regs *)&kframe->uc.uc_mcontext;
}
return NULL;
}
static int KBacktraceIterator_is_sigreturn(struct KBacktraceIterator *kbt)
{
return is_sigreturn(kbt->it.pc);
}
static int KBacktraceIterator_restart(struct KBacktraceIterator *kbt)
{
struct pt_regs *p;
struct rt_sigframe kframe;
p = valid_fault_handler(kbt);
if (p == NULL)
p = valid_sigframe(kbt, &kframe);
if (p == NULL)
return 0;
backtrace_init(&kbt->it, read_memory_func, kbt,
p->pc, p->lr, p->sp, p->regs[52]);
kbt->new_context = 1;
return 1;
}
/* Find a frame that isn't a sigreturn, if there is one. */
static int KBacktraceIterator_next_item_inclusive(
struct KBacktraceIterator *kbt)
{
for (;;) {
do {
if (!KBacktraceIterator_is_sigreturn(kbt))
return KBT_ONGOING;
} while (backtrace_next(&kbt->it));
if (!KBacktraceIterator_restart(kbt))
return KBT_DONE;
}
}
/*
* If the current sp is on a page different than what we recorded
* as the top-of-kernel-stack last time we context switched, we have
* probably blown the stack, and nothing is going to work out well.
* If we can at least get out a warning, that may help the debug,
* though we probably won't be able to backtrace into the code that
* actually did the recursive damage.
*/
static void validate_stack(struct pt_regs *regs)
{
int cpu = smp_processor_id();
unsigned long ksp0 = get_current_ksp0();
unsigned long ksp0_base = ksp0 - THREAD_SIZE;
unsigned long sp = stack_pointer;
if (EX1_PL(regs->ex1) == KERNEL_PL && regs->sp >= ksp0) {
pr_err("WARNING: cpu %d: kernel stack page %#lx underrun!\n"
" sp %#lx (%#lx in caller), caller pc %#lx, lr %#lx\n",
cpu, ksp0_base, sp, regs->sp, regs->pc, regs->lr);
}
else if (sp < ksp0_base + sizeof(struct thread_info)) {
pr_err("WARNING: cpu %d: kernel stack page %#lx overrun!\n"
" sp %#lx (%#lx in caller), caller pc %#lx, lr %#lx\n",
cpu, ksp0_base, sp, regs->sp, regs->pc, regs->lr);
}
}
void KBacktraceIterator_init(struct KBacktraceIterator *kbt,
struct task_struct *t, struct pt_regs *regs)
{
unsigned long pc, lr, sp, r52;
int is_current;
/*
* Set up callback information. We grab the kernel stack base
* so we will allow reads of that address range.
*/
is_current = (t == NULL || t == current);
kbt->is_current = is_current;
if (is_current)
t = validate_current();
kbt->task = t;
kbt->verbose = 0; /* override in caller if desired */
kbt->profile = 0; /* override in caller if desired */
kbt->end = KBT_ONGOING;
kbt->new_context = 1;
if (is_current)
validate_stack(regs);
if (regs == NULL) {
if (is_current || t->state == TASK_RUNNING) {
/* Can't do this; we need registers */
kbt->end = KBT_RUNNING;
return;
}
pc = get_switch_to_pc();
lr = t->thread.pc;
sp = t->thread.ksp;
r52 = 0;
} else {
pc = regs->pc;
lr = regs->lr;
sp = regs->sp;
r52 = regs->regs[52];
}
backtrace_init(&kbt->it, read_memory_func, kbt, pc, lr, sp, r52);
kbt->end = KBacktraceIterator_next_item_inclusive(kbt);
}
EXPORT_SYMBOL(KBacktraceIterator_init);
int KBacktraceIterator_end(struct KBacktraceIterator *kbt)
{
return kbt->end != KBT_ONGOING;
}
EXPORT_SYMBOL(KBacktraceIterator_end);
void KBacktraceIterator_next(struct KBacktraceIterator *kbt)
{
unsigned long old_pc = kbt->it.pc, old_sp = kbt->it.sp;
kbt->new_context = 0;
if (!backtrace_next(&kbt->it) && !KBacktraceIterator_restart(kbt)) {
kbt->end = KBT_DONE;
return;
}
kbt->end = KBacktraceIterator_next_item_inclusive(kbt);
if (old_pc == kbt->it.pc && old_sp == kbt->it.sp) {
/* Trapped in a loop; give up. */
kbt->end = KBT_LOOP;
}
}
EXPORT_SYMBOL(KBacktraceIterator_next);
static void describe_addr(struct KBacktraceIterator *kbt,
unsigned long address,
int have_mmap_sem, char *buf, size_t bufsize)
{
struct vm_area_struct *vma;
size_t namelen, remaining;
unsigned long size, offset, adjust;
char *p, *modname;
const char *name;
int rc;
/*
* Look one byte back for every caller frame (i.e. those that
* aren't a new context) so we look up symbol data for the
* call itself, not the following instruction, which may be on
* a different line (or in a different function).
*/
adjust = !kbt->new_context;
address -= adjust;
if (address >= PAGE_OFFSET) {
/* Handle kernel symbols. */
BUG_ON(bufsize < KSYM_NAME_LEN);
name = kallsyms_lookup(address, &size, &offset,
&modname, buf);
if (name == NULL) {
buf[0] = '\0';
return;
}
namelen = strlen(buf);
remaining = (bufsize - 1) - namelen;
p = buf + namelen;
rc = snprintf(p, remaining, "+%#lx/%#lx ",
offset + adjust, size);
if (modname && rc < remaining)
snprintf(p + rc, remaining - rc, "[%s] ", modname);
buf[bufsize-1] = '\0';
return;
}
/* If we don't have the mmap_sem, we can't show any more info. */
buf[0] = '\0';
if (!have_mmap_sem)
return;
/* Find vma info. */
vma = find_vma(kbt->task->mm, address);
if (vma == NULL || address < vma->vm_start) {
snprintf(buf, bufsize, "[unmapped address] ");
return;
}
if (vma->vm_file) {
char *s;
p = d_path(&vma->vm_file->f_path, buf, bufsize);
if (IS_ERR(p))
p = "?";
s = strrchr(p, '/');
if (s)
p = s+1;
} else {
p = "anon";
}
/* Generate a string description of the vma info. */
namelen = strlen(p);
remaining = (bufsize - 1) - namelen;
memmove(buf, p, namelen);
snprintf(buf + namelen, remaining, "[%lx+%lx] ",
vma->vm_start, vma->vm_end - vma->vm_start);
}
/*
* This method wraps the backtracer's more generic support.
* It is only invoked from the architecture-specific code; show_stack()
* and dump_stack() (in entry.S) are architecture-independent entry points.
*/
void tile_show_stack(struct KBacktraceIterator *kbt, int headers)
{
int i;
int have_mmap_sem = 0;
if (headers) {
/*
* Add a blank line since if we are called from panic(),
* then bust_spinlocks() spit out a space in front of us
* and it will mess up our KERN_ERR.
*/
pr_err("\n");
pr_err("Starting stack dump of tid %d, pid %d (%s)"
" on cpu %d at cycle %lld\n",
kbt->task->pid, kbt->task->tgid, kbt->task->comm,
smp_processor_id(), get_cycles());
}
kbt->verbose = 1;
i = 0;
for (; !KBacktraceIterator_end(kbt); KBacktraceIterator_next(kbt)) {
char namebuf[KSYM_NAME_LEN+100];
unsigned long address = kbt->it.pc;
/* Try to acquire the mmap_sem as we pass into userspace. */
if (address < PAGE_OFFSET && !have_mmap_sem && kbt->task->mm)
have_mmap_sem =
down_read_trylock(&kbt->task->mm->mmap_sem);
describe_addr(kbt, address, have_mmap_sem,
namebuf, sizeof(namebuf));
pr_err(" frame %d: 0x%lx %s(sp 0x%lx)\n",
i++, address, namebuf, (unsigned long)(kbt->it.sp));
if (i >= 100) {
pr_err("Stack dump truncated"
" (%d frames)\n", i);
break;
}
}
if (kbt->end == KBT_LOOP)
pr_err("Stack dump stopped; next frame identical to this one\n");
if (headers)
pr_err("Stack dump complete\n");
if (have_mmap_sem)
up_read(&kbt->task->mm->mmap_sem);
}
EXPORT_SYMBOL(tile_show_stack);
/* This is called from show_regs() and _dump_stack() */
void dump_stack_regs(struct pt_regs *regs)
{
struct KBacktraceIterator kbt;
KBacktraceIterator_init(&kbt, NULL, regs);
tile_show_stack(&kbt, 1);
}
EXPORT_SYMBOL(dump_stack_regs);
static struct pt_regs *regs_to_pt_regs(struct pt_regs *regs,
ulong pc, ulong lr, ulong sp, ulong r52)
{
memset(regs, 0, sizeof(struct pt_regs));
regs->pc = pc;
regs->lr = lr;
regs->sp = sp;
regs->regs[52] = r52;
return regs;
}
/* This is called from dump_stack() and just converts to pt_regs */
void _dump_stack(int dummy, ulong pc, ulong lr, ulong sp, ulong r52)
{
struct pt_regs regs;
dump_stack_regs(regs_to_pt_regs(&regs, pc, lr, sp, r52));
}
/* This is called from KBacktraceIterator_init_current() */
void _KBacktraceIterator_init_current(struct KBacktraceIterator *kbt, ulong pc,
ulong lr, ulong sp, ulong r52)
{
struct pt_regs regs;
KBacktraceIterator_init(kbt, NULL,
regs_to_pt_regs(&regs, pc, lr, sp, r52));
}
/* This is called only from kernel/sched.c, with esp == NULL */
void show_stack(struct task_struct *task, unsigned long *esp)
{
struct KBacktraceIterator kbt;
if (task == NULL || task == current)
KBacktraceIterator_init_current(&kbt);
else
KBacktraceIterator_init(&kbt, task, NULL);
tile_show_stack(&kbt, 0);
}
#ifdef CONFIG_STACKTRACE
/* Support generic Linux stack API too */
void save_stack_trace_tsk(struct task_struct *task, struct stack_trace *trace)
{
struct KBacktraceIterator kbt;
int skip = trace->skip;
int i = 0;
if (task == NULL || task == current)
KBacktraceIterator_init_current(&kbt);
else
KBacktraceIterator_init(&kbt, task, NULL);
for (; !KBacktraceIterator_end(&kbt); KBacktraceIterator_next(&kbt)) {
if (skip) {
--skip;
continue;
}
if (i >= trace->max_entries || kbt.it.pc < PAGE_OFFSET)
break;
trace->entries[i++] = kbt.it.pc;
}
trace->nr_entries = i;
}
EXPORT_SYMBOL(save_stack_trace_tsk);
void save_stack_trace(struct stack_trace *trace)
{
save_stack_trace_tsk(NULL, trace);
}
EXPORT_SYMBOL_GPL(save_stack_trace);
#endif
/* In entry.S */
EXPORT_SYMBOL(KBacktraceIterator_init_current);

121
arch/tile/kernel/sys.c Normal file
View File

@@ -0,0 +1,121 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* This file contains various random system calls that
* have a non-standard calling sequence on the Linux/TILE
* platform.
*/
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/syscalls.h>
#include <linux/mman.h>
#include <linux/file.h>
#include <linux/mempolicy.h>
#include <linux/binfmts.h>
#include <linux/fs.h>
#include <linux/compat.h>
#include <linux/uaccess.h>
#include <linux/signal.h>
#include <asm/syscalls.h>
#include <asm/pgtable.h>
#include <asm/homecache.h>
#include <asm/cachectl.h>
#include <arch/chip.h>
SYSCALL_DEFINE3(cacheflush, unsigned long, addr, unsigned long, len,
unsigned long, flags)
{
if (flags & DCACHE)
homecache_evict(cpumask_of(smp_processor_id()));
if (flags & ICACHE)
flush_remote(0, HV_FLUSH_EVICT_L1I, mm_cpumask(current->mm),
0, 0, 0, NULL, NULL, 0);
return 0;
}
/*
* Syscalls that pass 64-bit values on 32-bit systems normally
* pass them as (low,high) word packed into the immediately adjacent
* registers. If the low word naturally falls on an even register,
* our ABI makes it work correctly; if not, we adjust it here.
* Handling it here means we don't have to fix uclibc AND glibc AND
* any other standard libcs we want to support.
*/
#if !defined(__tilegx__) || defined(CONFIG_COMPAT)
ssize_t sys32_readahead(int fd, u32 offset_lo, u32 offset_hi, u32 count)
{
return sys_readahead(fd, ((loff_t)offset_hi << 32) | offset_lo, count);
}
int sys32_fadvise64_64(int fd, u32 offset_lo, u32 offset_hi,
u32 len_lo, u32 len_hi, int advice)
{
return sys_fadvise64_64(fd, ((loff_t)offset_hi << 32) | offset_lo,
((loff_t)len_hi << 32) | len_lo, advice);
}
#endif /* 32-bit syscall wrappers */
/* Note: used by the compat code even in 64-bit Linux. */
SYSCALL_DEFINE6(mmap2, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, off_4k)
{
#define PAGE_ADJUST (PAGE_SHIFT - 12)
if (off_4k & ((1 << PAGE_ADJUST) - 1))
return -EINVAL;
return sys_mmap_pgoff(addr, len, prot, flags, fd,
off_4k >> PAGE_ADJUST);
}
#ifdef __tilegx__
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, off_t, offset)
{
if (offset & ((1 << PAGE_SHIFT) - 1))
return -EINVAL;
return sys_mmap_pgoff(addr, len, prot, flags, fd,
offset >> PAGE_SHIFT);
}
#endif
/* Provide the actual syscall number to call mapping. */
#undef __SYSCALL
#define __SYSCALL(nr, call) [nr] = (call),
#ifndef __tilegx__
/* See comments at the top of the file. */
#define sys_fadvise64_64 sys32_fadvise64_64
#define sys_readahead sys32_readahead
#endif
/* Call the assembly trampolines where necessary. */
#undef sys_rt_sigreturn
#define sys_rt_sigreturn _sys_rt_sigreturn
#define sys_clone _sys_clone
/*
* 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>
};

193
arch/tile/kernel/sysfs.c Normal file
View File

@@ -0,0 +1,193 @@
/*
* Copyright 2011 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* /sys entry support.
*/
#include <linux/device.h>
#include <linux/cpu.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/stat.h>
#include <hv/hypervisor.h>
/* Return a string queried from the hypervisor, truncated to page size. */
static ssize_t get_hv_confstr(char *page, int query)
{
ssize_t n = hv_confstr(query, (unsigned long)page, PAGE_SIZE - 1);
n = n < 0 ? 0 : min(n, (ssize_t)PAGE_SIZE - 1) - 1;
if (n)
page[n++] = '\n';
page[n] = '\0';
return n;
}
static ssize_t chip_width_show(struct device *dev,
struct device_attribute *attr,
char *page)
{
return sprintf(page, "%u\n", smp_width);
}
static DEVICE_ATTR(chip_width, 0444, chip_width_show, NULL);
static ssize_t chip_height_show(struct device *dev,
struct device_attribute *attr,
char *page)
{
return sprintf(page, "%u\n", smp_height);
}
static DEVICE_ATTR(chip_height, 0444, chip_height_show, NULL);
static ssize_t chip_serial_show(struct device *dev,
struct device_attribute *attr,
char *page)
{
return get_hv_confstr(page, HV_CONFSTR_CHIP_SERIAL_NUM);
}
static DEVICE_ATTR(chip_serial, 0444, chip_serial_show, NULL);
static ssize_t chip_revision_show(struct device *dev,
struct device_attribute *attr,
char *page)
{
return get_hv_confstr(page, HV_CONFSTR_CHIP_REV);
}
static DEVICE_ATTR(chip_revision, 0444, chip_revision_show, NULL);
static ssize_t type_show(struct device *dev,
struct device_attribute *attr,
char *page)
{
return sprintf(page, "tilera\n");
}
static DEVICE_ATTR(type, 0444, type_show, NULL);
#define HV_CONF_ATTR(name, conf) \
static ssize_t name ## _show(struct device *dev, \
struct device_attribute *attr, \
char *page) \
{ \
return get_hv_confstr(page, conf); \
} \
static DEVICE_ATTR(name, 0444, name ## _show, NULL);
HV_CONF_ATTR(version, HV_CONFSTR_HV_SW_VER)
HV_CONF_ATTR(config_version, HV_CONFSTR_HV_CONFIG_VER)
HV_CONF_ATTR(board_part, HV_CONFSTR_BOARD_PART_NUM)
HV_CONF_ATTR(board_serial, HV_CONFSTR_BOARD_SERIAL_NUM)
HV_CONF_ATTR(board_revision, HV_CONFSTR_BOARD_REV)
HV_CONF_ATTR(board_description, HV_CONFSTR_BOARD_DESC)
HV_CONF_ATTR(mezz_part, HV_CONFSTR_MEZZ_PART_NUM)
HV_CONF_ATTR(mezz_serial, HV_CONFSTR_MEZZ_SERIAL_NUM)
HV_CONF_ATTR(mezz_revision, HV_CONFSTR_MEZZ_REV)
HV_CONF_ATTR(mezz_description, HV_CONFSTR_MEZZ_DESC)
HV_CONF_ATTR(cpumod_part, HV_CONFSTR_CPUMOD_PART_NUM)
HV_CONF_ATTR(cpumod_serial, HV_CONFSTR_CPUMOD_SERIAL_NUM)
HV_CONF_ATTR(cpumod_revision, HV_CONFSTR_CPUMOD_REV)
HV_CONF_ATTR(cpumod_description,HV_CONFSTR_CPUMOD_DESC)
HV_CONF_ATTR(switch_control, HV_CONFSTR_SWITCH_CONTROL)
static struct attribute *board_attrs[] = {
&dev_attr_board_part.attr,
&dev_attr_board_serial.attr,
&dev_attr_board_revision.attr,
&dev_attr_board_description.attr,
&dev_attr_mezz_part.attr,
&dev_attr_mezz_serial.attr,
&dev_attr_mezz_revision.attr,
&dev_attr_mezz_description.attr,
&dev_attr_cpumod_part.attr,
&dev_attr_cpumod_serial.attr,
&dev_attr_cpumod_revision.attr,
&dev_attr_cpumod_description.attr,
&dev_attr_switch_control.attr,
NULL
};
static struct attribute_group board_attr_group = {
.name = "board",
.attrs = board_attrs,
};
static struct bin_attribute hvconfig_bin;
static ssize_t
hvconfig_bin_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t off, size_t count)
{
static size_t size;
/* Lazily learn the true size (minus the trailing NUL). */
if (size == 0)
size = hv_confstr(HV_CONFSTR_HV_CONFIG, 0, 0) - 1;
/* Check and adjust input parameters. */
if (off > size)
return -EINVAL;
if (count > size - off)
count = size - off;
if (count) {
/* Get a copy of the hvc and copy out the relevant portion. */
char *hvc;
size = off + count;
hvc = kmalloc(size, GFP_KERNEL);
if (hvc == NULL)
return -ENOMEM;
hv_confstr(HV_CONFSTR_HV_CONFIG, (unsigned long)hvc, size);
memcpy(buf, hvc + off, count);
kfree(hvc);
}
return count;
}
static int __init create_sysfs_entries(void)
{
int err = 0;
#define create_cpu_attr(name) \
if (!err) \
err = device_create_file(cpu_subsys.dev_root, &dev_attr_##name);
create_cpu_attr(chip_width);
create_cpu_attr(chip_height);
create_cpu_attr(chip_serial);
create_cpu_attr(chip_revision);
#define create_hv_attr(name) \
if (!err) \
err = sysfs_create_file(hypervisor_kobj, &dev_attr_##name.attr);
create_hv_attr(type);
create_hv_attr(version);
create_hv_attr(config_version);
if (!err)
err = sysfs_create_group(hypervisor_kobj, &board_attr_group);
if (!err) {
sysfs_bin_attr_init(&hvconfig_bin);
hvconfig_bin.attr.name = "hvconfig";
hvconfig_bin.attr.mode = S_IRUGO;
hvconfig_bin.read = hvconfig_bin_read;
hvconfig_bin.size = PAGE_SIZE;
err = sysfs_create_bin_file(hypervisor_kobj, &hvconfig_bin);
}
return err;
}
subsys_initcall(create_sysfs_entries);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

274
arch/tile/kernel/time.c Normal file
View File

@@ -0,0 +1,274 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* Support the cycle counter clocksource and tile timer clock event device.
*/
#include <linux/time.h>
#include <linux/timex.h>
#include <linux/clocksource.h>
#include <linux/clockchips.h>
#include <linux/hardirq.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/timekeeper_internal.h>
#include <asm/irq_regs.h>
#include <asm/traps.h>
#include <asm/vdso.h>
#include <hv/hypervisor.h>
#include <arch/interrupts.h>
#include <arch/spr_def.h>
/*
* Define the cycle counter clock source.
*/
/* How many cycles per second we are running at. */
static cycles_t cycles_per_sec __write_once;
cycles_t get_clock_rate(void)
{
return cycles_per_sec;
}
#if CHIP_HAS_SPLIT_CYCLE()
cycles_t get_cycles(void)
{
unsigned int high = __insn_mfspr(SPR_CYCLE_HIGH);
unsigned int low = __insn_mfspr(SPR_CYCLE_LOW);
unsigned int high2 = __insn_mfspr(SPR_CYCLE_HIGH);
while (unlikely(high != high2)) {
low = __insn_mfspr(SPR_CYCLE_LOW);
high = high2;
high2 = __insn_mfspr(SPR_CYCLE_HIGH);
}
return (((cycles_t)high) << 32) | low;
}
EXPORT_SYMBOL(get_cycles);
#endif
/*
* We use a relatively small shift value so that sched_clock()
* won't wrap around very often.
*/
#define SCHED_CLOCK_SHIFT 10
static unsigned long sched_clock_mult __write_once;
static cycles_t clocksource_get_cycles(struct clocksource *cs)
{
return get_cycles();
}
static struct clocksource cycle_counter_cs = {
.name = "cycle counter",
.rating = 300,
.read = clocksource_get_cycles,
.mask = CLOCKSOURCE_MASK(64),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
/*
* Called very early from setup_arch() to set cycles_per_sec.
* We initialize it early so we can use it to set up loops_per_jiffy.
*/
void __init setup_clock(void)
{
cycles_per_sec = hv_sysconf(HV_SYSCONF_CPU_SPEED);
sched_clock_mult =
clocksource_hz2mult(cycles_per_sec, SCHED_CLOCK_SHIFT);
}
void __init calibrate_delay(void)
{
loops_per_jiffy = get_clock_rate() / HZ;
pr_info("Clock rate yields %lu.%02lu BogoMIPS (lpj=%lu)\n",
loops_per_jiffy/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100, loops_per_jiffy);
}
/* Called fairly late in init/main.c, but before we go smp. */
void __init time_init(void)
{
/* Initialize and register the clock source. */
clocksource_register_hz(&cycle_counter_cs, cycles_per_sec);
/* Start up the tile-timer interrupt source on the boot cpu. */
setup_tile_timer();
}
/*
* Define the tile timer clock event device. The timer is driven by
* the TILE_TIMER_CONTROL register, which consists of a 31-bit down
* counter, plus bit 31, which signifies that the counter has wrapped
* from zero to (2**31) - 1. The INT_TILE_TIMER interrupt will be
* raised as long as bit 31 is set.
*
* The TILE_MINSEC value represents the largest range of real-time
* we can possibly cover with the timer, based on MAX_TICK combined
* with the slowest reasonable clock rate we might run at.
*/
#define MAX_TICK 0x7fffffff /* we have 31 bits of countdown timer */
#define TILE_MINSEC 5 /* timer covers no more than 5 seconds */
static int tile_timer_set_next_event(unsigned long ticks,
struct clock_event_device *evt)
{
BUG_ON(ticks > MAX_TICK);
__insn_mtspr(SPR_TILE_TIMER_CONTROL, ticks);
arch_local_irq_unmask_now(INT_TILE_TIMER);
return 0;
}
/*
* Whenever anyone tries to change modes, we just mask interrupts
* and wait for the next event to get set.
*/
static void tile_timer_set_mode(enum clock_event_mode mode,
struct clock_event_device *evt)
{
arch_local_irq_mask_now(INT_TILE_TIMER);
}
/*
* Set min_delta_ns to 1 microsecond, since it takes about
* that long to fire the interrupt.
*/
static DEFINE_PER_CPU(struct clock_event_device, tile_timer) = {
.name = "tile timer",
.features = CLOCK_EVT_FEAT_ONESHOT,
.min_delta_ns = 1000,
.rating = 100,
.irq = -1,
.set_next_event = tile_timer_set_next_event,
.set_mode = tile_timer_set_mode,
};
void __cpuinit setup_tile_timer(void)
{
struct clock_event_device *evt = &__get_cpu_var(tile_timer);
/* Fill in fields that are speed-specific. */
clockevents_calc_mult_shift(evt, cycles_per_sec, TILE_MINSEC);
evt->max_delta_ns = clockevent_delta2ns(MAX_TICK, evt);
/* Mark as being for this cpu only. */
evt->cpumask = cpumask_of(smp_processor_id());
/* Start out with timer not firing. */
arch_local_irq_mask_now(INT_TILE_TIMER);
/* Register tile timer. */
clockevents_register_device(evt);
}
/* Called from the interrupt vector. */
void do_timer_interrupt(struct pt_regs *regs, int fault_num)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct clock_event_device *evt = &__get_cpu_var(tile_timer);
/*
* Mask the timer interrupt here, since we are a oneshot timer
* and there are now by definition no events pending.
*/
arch_local_irq_mask(INT_TILE_TIMER);
/* Track time spent here in an interrupt context */
irq_enter();
/* Track interrupt count. */
__get_cpu_var(irq_stat).irq_timer_count++;
/* Call the generic timer handler */
evt->event_handler(evt);
/*
* Track time spent against the current process again and
* process any softirqs if they are waiting.
*/
irq_exit();
set_irq_regs(old_regs);
}
/*
* Scheduler clock - returns current time in nanosec units.
* Note that with LOCKDEP, this is called during lockdep_init(), and
* we will claim that sched_clock() is zero for a little while, until
* we run setup_clock(), above.
*/
unsigned long long sched_clock(void)
{
return clocksource_cyc2ns(get_cycles(),
sched_clock_mult, SCHED_CLOCK_SHIFT);
}
int setup_profiling_timer(unsigned int multiplier)
{
return -EINVAL;
}
/*
* Use the tile timer to convert nsecs to core clock cycles, relying
* on it having the same frequency as SPR_CYCLE.
*/
cycles_t ns2cycles(unsigned long nsecs)
{
/*
* We do not have to disable preemption here as each core has the same
* clock frequency.
*/
struct clock_event_device *dev = &__raw_get_cpu_var(tile_timer);
return ((u64)nsecs * dev->mult) >> dev->shift;
}
void update_vsyscall_tz(void)
{
/* Userspace gettimeofday will spin while this value is odd. */
++vdso_data->tz_update_count;
smp_wmb();
vdso_data->tz_minuteswest = sys_tz.tz_minuteswest;
vdso_data->tz_dsttime = sys_tz.tz_dsttime;
smp_wmb();
++vdso_data->tz_update_count;
}
void update_vsyscall(struct timekeeper *tk)
{
struct timespec wall_time = tk_xtime(tk);
struct timespec *wtm = &tk->wall_to_monotonic;
struct clocksource *clock = tk->tkr.clock;
if (clock != &cycle_counter_cs)
return;
/* Userspace gettimeofday will spin while this value is odd. */
++vdso_data->tb_update_count;
smp_wmb();
vdso_data->xtime_tod_stamp = tk->tkr.cycle_last;
vdso_data->xtime_clock_sec = tk->xtime_sec;
vdso_data->xtime_clock_nsec = tk->tkr.xtime_nsec;
vdso_data->wtom_clock_sec = wtm->tv_sec;
vdso_data->wtom_clock_nsec = wtm->tv_nsec;
vdso_data->mult = tk->tkr.mult;
vdso_data->shift = tk->tkr.shift;
smp_wmb();
++vdso_data->tb_update_count;
}

98
arch/tile/kernel/tlb.c Normal file
View File

@@ -0,0 +1,98 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
*/
#include <linux/cpumask.h>
#include <linux/module.h>
#include <linux/hugetlb.h>
#include <asm/tlbflush.h>
#include <asm/homecache.h>
#include <hv/hypervisor.h>
/* From tlbflush.h */
DEFINE_PER_CPU(int, current_asid);
int min_asid, max_asid;
/*
* Note that we flush the L1I (for VM_EXEC pages) as well as the TLB
* so that when we are unmapping an executable page, we also flush it.
* Combined with flushing the L1I at context switch time, this means
* we don't have to do any other icache flushes.
*/
void flush_tlb_mm(struct mm_struct *mm)
{
HV_Remote_ASID asids[NR_CPUS];
int i = 0, cpu;
for_each_cpu(cpu, mm_cpumask(mm)) {
HV_Remote_ASID *asid = &asids[i++];
asid->y = cpu / smp_topology.width;
asid->x = cpu % smp_topology.width;
asid->asid = per_cpu(current_asid, cpu);
}
flush_remote(0, HV_FLUSH_EVICT_L1I, mm_cpumask(mm),
0, 0, 0, NULL, asids, i);
}
void flush_tlb_current_task(void)
{
flush_tlb_mm(current->mm);
}
void flush_tlb_page_mm(struct vm_area_struct *vma, struct mm_struct *mm,
unsigned long va)
{
unsigned long size = vma_kernel_pagesize(vma);
int cache = (vma->vm_flags & VM_EXEC) ? HV_FLUSH_EVICT_L1I : 0;
flush_remote(0, cache, mm_cpumask(mm),
va, size, size, mm_cpumask(mm), NULL, 0);
}
void flush_tlb_page(struct vm_area_struct *vma, unsigned long va)
{
flush_tlb_page_mm(vma, vma->vm_mm, va);
}
EXPORT_SYMBOL(flush_tlb_page);
void flush_tlb_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
unsigned long size = vma_kernel_pagesize(vma);
struct mm_struct *mm = vma->vm_mm;
int cache = (vma->vm_flags & VM_EXEC) ? HV_FLUSH_EVICT_L1I : 0;
flush_remote(0, cache, mm_cpumask(mm), start, end - start, size,
mm_cpumask(mm), NULL, 0);
}
void flush_tlb_all(void)
{
int i;
for (i = 0; ; ++i) {
HV_VirtAddrRange r = hv_inquire_virtual(i);
if (r.size == 0)
break;
flush_remote(0, HV_FLUSH_EVICT_L1I, cpu_online_mask,
r.start, r.size, PAGE_SIZE, cpu_online_mask,
NULL, 0);
flush_remote(0, 0, NULL,
r.start, r.size, HPAGE_SIZE, cpu_online_mask,
NULL, 0);
}
}
void flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
flush_remote(0, HV_FLUSH_EVICT_L1I, cpu_online_mask,
start, end - start, PAGE_SIZE, cpu_online_mask, NULL, 0);
}

355
arch/tile/kernel/traps.c Normal file
View File

@@ -0,0 +1,355 @@
/*
* Copyright 2010 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/uaccess.h>
#include <linux/ptrace.h>
#include <asm/stack.h>
#include <asm/traps.h>
#include <asm/setup.h>
#include <arch/interrupts.h>
#include <arch/spr_def.h>
#include <arch/opcode.h>
void __init trap_init(void)
{
/* Nothing needed here since we link code at .intrpt1 */
}
int unaligned_fixup = 1;
static int __init setup_unaligned_fixup(char *str)
{
/*
* Say "=-1" to completely disable it. If you just do "=0", we
* will still parse the instruction, then fire a SIGBUS with
* the correct address from inside the single_step code.
*/
long val;
if (strict_strtol(str, 0, &val) != 0)
return 0;
unaligned_fixup = val;
pr_info("Fixups for unaligned data accesses are %s\n",
unaligned_fixup >= 0 ?
(unaligned_fixup ? "enabled" : "disabled") :
"completely disabled");
return 1;
}
__setup("unaligned_fixup=", setup_unaligned_fixup);
#if CHIP_HAS_TILE_DMA()
static int dma_disabled;
static int __init nodma(char *str)
{
pr_info("User-space DMA is disabled\n");
dma_disabled = 1;
return 1;
}
__setup("nodma", nodma);
/* How to decode SPR_GPV_REASON */
#define IRET_ERROR (1U << 31)
#define MT_ERROR (1U << 30)
#define MF_ERROR (1U << 29)
#define SPR_INDEX ((1U << 15) - 1)
#define SPR_MPL_SHIFT 9 /* starting bit position for MPL encoded in SPR */
/*
* See if this GPV is just to notify the kernel of SPR use and we can
* retry the user instruction after adjusting some MPLs suitably.
*/
static int retry_gpv(unsigned int gpv_reason)
{
int mpl;
if (gpv_reason & IRET_ERROR)
return 0;
BUG_ON((gpv_reason & (MT_ERROR|MF_ERROR)) == 0);
mpl = (gpv_reason & SPR_INDEX) >> SPR_MPL_SHIFT;
if (mpl == INT_DMA_NOTIFY && !dma_disabled) {
/* User is turning on DMA. Allow it and retry. */
printk(KERN_DEBUG "Process %d/%s is now enabled for DMA\n",
current->pid, current->comm);
BUG_ON(current->thread.tile_dma_state.enabled);
current->thread.tile_dma_state.enabled = 1;
grant_dma_mpls();
return 1;
}
return 0;
}
#endif /* CHIP_HAS_TILE_DMA() */
#ifdef __tilegx__
#define bundle_bits tilegx_bundle_bits
#else
#define bundle_bits tile_bundle_bits
#endif
extern bundle_bits bpt_code;
asm(".pushsection .rodata.bpt_code,\"a\";"
".align 8;"
"bpt_code: bpt;"
".size bpt_code,.-bpt_code;"
".popsection");
static int special_ill(bundle_bits bundle, int *sigp, int *codep)
{
int sig, code, maxcode;
if (bundle == bpt_code) {
*sigp = SIGTRAP;
*codep = TRAP_BRKPT;
return 1;
}
/* If it's a "raise" bundle, then "ill" must be in pipe X1. */
#ifdef __tilegx__
if ((bundle & TILEGX_BUNDLE_MODE_MASK) != 0)
return 0;
if (get_Opcode_X1(bundle) != RRR_0_OPCODE_X1)
return 0;
if (get_RRROpcodeExtension_X1(bundle) != UNARY_RRR_0_OPCODE_X1)
return 0;
if (get_UnaryOpcodeExtension_X1(bundle) != ILL_UNARY_OPCODE_X1)
return 0;
#else
if (bundle & TILEPRO_BUNDLE_Y_ENCODING_MASK)
return 0;
if (get_Opcode_X1(bundle) != SHUN_0_OPCODE_X1)
return 0;
if (get_UnShOpcodeExtension_X1(bundle) != UN_0_SHUN_0_OPCODE_X1)
return 0;
if (get_UnOpcodeExtension_X1(bundle) != ILL_UN_0_SHUN_0_OPCODE_X1)
return 0;
#endif
/* Check that the magic distinguishers are set to mean "raise". */
if (get_Dest_X1(bundle) != 29 || get_SrcA_X1(bundle) != 37)
return 0;
/* There must be an "addli zero, zero, VAL" in X0. */
if (get_Opcode_X0(bundle) != ADDLI_OPCODE_X0)
return 0;
if (get_Dest_X0(bundle) != TREG_ZERO)
return 0;
if (get_SrcA_X0(bundle) != TREG_ZERO)
return 0;
/*
* Validate the proposed signal number and si_code value.
* Note that we embed these in the static instruction itself
* so that we perturb the register state as little as possible
* at the time of the actual fault; it's unlikely you'd ever
* need to dynamically choose which kind of fault to raise
* from user space.
*/
sig = get_Imm16_X0(bundle) & 0x3f;
switch (sig) {
case SIGILL:
maxcode = NSIGILL;
break;
case SIGFPE:
maxcode = NSIGFPE;
break;
case SIGSEGV:
maxcode = NSIGSEGV;
break;
case SIGBUS:
maxcode = NSIGBUS;
break;
case SIGTRAP:
maxcode = NSIGTRAP;
break;
default:
return 0;
}
code = (get_Imm16_X0(bundle) >> 6) & 0xf;
if (code <= 0 || code > maxcode)
return 0;
/* Make it the requested signal. */
*sigp = sig;
*codep = code | __SI_FAULT;
return 1;
}
static const char *const int_name[] = {
[INT_MEM_ERROR] = "Memory error",
[INT_ILL] = "Illegal instruction",
[INT_GPV] = "General protection violation",
[INT_UDN_ACCESS] = "UDN access",
[INT_IDN_ACCESS] = "IDN access",
#if CHIP_HAS_SN()
[INT_SN_ACCESS] = "SN access",
#endif
[INT_SWINT_3] = "Software interrupt 3",
[INT_SWINT_2] = "Software interrupt 2",
[INT_SWINT_0] = "Software interrupt 0",
[INT_UNALIGN_DATA] = "Unaligned data",
[INT_DOUBLE_FAULT] = "Double fault",
#ifdef __tilegx__
[INT_ILL_TRANS] = "Illegal virtual address",
#endif
};
void __kprobes do_trap(struct pt_regs *regs, int fault_num,
unsigned long reason)
{
siginfo_t info = { 0 };
int signo, code;
unsigned long address = 0;
bundle_bits instr;
/* Re-enable interrupts. */
local_irq_enable();
/*
* If it hits in kernel mode and we can't fix it up, just exit the
* current process and hope for the best.
*/
if (!user_mode(regs)) {
const char *name;
if (fixup_exception(regs)) /* only UNALIGN_DATA in practice */
return;
if (fault_num >= 0 &&
fault_num < sizeof(int_name)/sizeof(int_name[0]) &&
int_name[fault_num] != NULL)
name = int_name[fault_num];
else
name = "Unknown interrupt";
pr_alert("Kernel took bad trap %d (%s) at PC %#lx\n",
fault_num, name, regs->pc);
if (fault_num == INT_GPV)
pr_alert("GPV_REASON is %#lx\n", reason);
show_regs(regs);
do_exit(SIGKILL); /* FIXME: implement i386 die() */
return;
}
switch (fault_num) {
case INT_MEM_ERROR:
signo = SIGBUS;
code = BUS_OBJERR;
break;
case INT_ILL:
if (copy_from_user(&instr, (void __user *)regs->pc,
sizeof(instr))) {
pr_err("Unreadable instruction for INT_ILL:"
" %#lx\n", regs->pc);
do_exit(SIGKILL);
return;
}
if (!special_ill(instr, &signo, &code)) {
signo = SIGILL;
code = ILL_ILLOPC;
}
address = regs->pc;
break;
case INT_GPV:
#if CHIP_HAS_TILE_DMA()
if (retry_gpv(reason))
return;
#endif
/*FALLTHROUGH*/
case INT_UDN_ACCESS:
case INT_IDN_ACCESS:
#if CHIP_HAS_SN()
case INT_SN_ACCESS:
#endif
signo = SIGILL;
code = ILL_PRVREG;
address = regs->pc;
break;
case INT_SWINT_3:
case INT_SWINT_2:
case INT_SWINT_0:
signo = SIGILL;
code = ILL_ILLTRP;
address = regs->pc;
break;
case INT_UNALIGN_DATA:
#ifndef __tilegx__ /* Emulated support for single step debugging */
if (unaligned_fixup >= 0) {
struct single_step_state *state =
current_thread_info()->step_state;
if (!state ||
(void __user *)(regs->pc) != state->buffer) {
single_step_once(regs);
return;
}
}
#endif
signo = SIGBUS;
code = BUS_ADRALN;
address = 0;
break;
case INT_DOUBLE_FAULT:
/*
* For double fault, "reason" is actually passed as
* SYSTEM_SAVE_K_2, the hypervisor's double-fault info, so
* we can provide the original fault number rather than
* the uninteresting "INT_DOUBLE_FAULT" so the user can
* learn what actually struck while PL0 ICS was set.
*/
fault_num = reason;
signo = SIGILL;
code = ILL_DBLFLT;
address = regs->pc;
break;
#ifdef __tilegx__
case INT_ILL_TRANS: {
/* Avoid a hardware erratum with the return address stack. */
fill_ra_stack();
signo = SIGSEGV;
code = SEGV_MAPERR;
if (reason & SPR_ILL_TRANS_REASON__I_STREAM_VA_RMASK)
address = regs->pc;
else
address = 0; /* FIXME: GX: single-step for address */
break;
}
#endif
default:
panic("Unexpected do_trap interrupt number %d", fault_num);
return;
}
info.si_signo = signo;
info.si_code = code;
info.si_addr = (void __user *)address;
if (signo == SIGILL)
info.si_trapno = fault_num;
if (signo != SIGTRAP)
trace_unhandled_signal("trap", regs, address, signo);
force_sig_info(signo, &info, current);
}
void kernel_double_fault(int dummy, ulong pc, ulong lr, ulong sp, ulong r52)
{
_dump_stack(dummy, pc, lr, sp, r52);
pr_emerg("Double fault: exiting\n");
machine_halt();
}

69
arch/tile/kernel/usb.c Normal file
View File

@@ -0,0 +1,69 @@
/*
* Copyright 2012 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* Register the Tile-Gx USB interfaces as platform devices.
*
* The actual USB driver is just some glue (in
* drivers/usb/host/[eo]hci-tilegx.c) which makes the registers available
* to the standard kernel EHCI and OHCI drivers.
*/
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/usb/tilegx.h>
#include <linux/types.h>
static u64 ehci_dmamask = DMA_BIT_MASK(32);
#define USB_HOST_DEF(unit, type, dmamask) \
static struct \
tilegx_usb_platform_data tilegx_usb_platform_data_ ## type ## \
hci ## unit = { \
.dev_index = unit, \
}; \
\
static struct platform_device tilegx_usb_ ## type ## hci ## unit = { \
.name = "tilegx-" #type "hci", \
.id = unit, \
.dev = { \
.dma_mask = dmamask, \
.coherent_dma_mask = DMA_BIT_MASK(32), \
.platform_data = \
&tilegx_usb_platform_data_ ## type ## hci ## \
unit, \
}, \
};
USB_HOST_DEF(0, e, &ehci_dmamask)
USB_HOST_DEF(0, o, NULL)
USB_HOST_DEF(1, e, &ehci_dmamask)
USB_HOST_DEF(1, o, NULL)
#undef USB_HOST_DEF
static struct platform_device *tilegx_usb_devices[] __initdata = {
&tilegx_usb_ehci0,
&tilegx_usb_ehci1,
&tilegx_usb_ohci0,
&tilegx_usb_ohci1,
};
/** Add our set of possible USB devices. */
static int __init tilegx_usb_init(void)
{
platform_add_devices(tilegx_usb_devices,
ARRAY_SIZE(tilegx_usb_devices));
return 0;
}
arch_initcall(tilegx_usb_init);

212
arch/tile/kernel/vdso.c Normal file
View File

@@ -0,0 +1,212 @@
/*
* Copyright 2012 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/binfmts.h>
#include <linux/compat.h>
#include <linux/elf.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <asm/vdso.h>
#include <asm/mman.h>
#include <asm/sections.h>
#include <arch/sim.h>
/* The alignment of the vDSO. */
#define VDSO_ALIGNMENT PAGE_SIZE
static unsigned int vdso_pages;
static struct page **vdso_pagelist;
#ifdef CONFIG_COMPAT
static unsigned int vdso32_pages;
static struct page **vdso32_pagelist;
#endif
static int vdso_ready;
/*
* The vdso data page.
*/
static union {
struct vdso_data data;
u8 page[PAGE_SIZE];
} vdso_data_store __page_aligned_data;
struct vdso_data *vdso_data = &vdso_data_store.data;
static unsigned int __read_mostly vdso_enabled = 1;
static struct page **vdso_setup(void *vdso_kbase, unsigned int pages)
{
int i;
struct page **pagelist;
pagelist = kzalloc(sizeof(struct page *) * (pages + 1), GFP_KERNEL);
BUG_ON(pagelist == NULL);
for (i = 0; i < pages - 1; i++) {
struct page *pg = virt_to_page(vdso_kbase + i*PAGE_SIZE);
ClearPageReserved(pg);
pagelist[i] = pg;
}
pagelist[pages - 1] = virt_to_page(vdso_data);
pagelist[pages] = NULL;
return pagelist;
}
static int __init vdso_init(void)
{
int data_pages = sizeof(vdso_data_store) >> PAGE_SHIFT;
/*
* We can disable vDSO support generally, but we need to retain
* one page to support the two-bundle (16-byte) rt_sigreturn path.
*/
if (!vdso_enabled) {
size_t offset = (unsigned long)&__vdso_rt_sigreturn;
static struct page *sigret_page;
sigret_page = alloc_page(GFP_KERNEL | __GFP_ZERO);
BUG_ON(sigret_page == NULL);
vdso_pagelist = &sigret_page;
vdso_pages = 1;
BUG_ON(offset >= PAGE_SIZE);
memcpy(page_address(sigret_page) + offset,
vdso_start + offset, 16);
#ifdef CONFIG_COMPAT
vdso32_pages = vdso_pages;
vdso32_pagelist = vdso_pagelist;
#endif
vdso_ready = 1;
return 0;
}
vdso_pages = (vdso_end - vdso_start) >> PAGE_SHIFT;
vdso_pages += data_pages;
vdso_pagelist = vdso_setup(vdso_start, vdso_pages);
#ifdef CONFIG_COMPAT
vdso32_pages = (vdso32_end - vdso32_start) >> PAGE_SHIFT;
vdso32_pages += data_pages;
vdso32_pagelist = vdso_setup(vdso32_start, vdso32_pages);
#endif
smp_wmb();
vdso_ready = 1;
return 0;
}
arch_initcall(vdso_init);
const char *arch_vma_name(struct vm_area_struct *vma)
{
if (vma->vm_mm && vma->vm_start == VDSO_BASE)
return "[vdso]";
#ifndef __tilegx__
if (vma->vm_start == MEM_USER_INTRPT)
return "[intrpt]";
#endif
return NULL;
}
struct vm_area_struct *get_gate_vma(struct mm_struct *mm)
{
return NULL;
}
int in_gate_area(struct mm_struct *mm, unsigned long address)
{
return 0;
}
int in_gate_area_no_mm(unsigned long address)
{
return 0;
}
int setup_vdso_pages(void)
{
struct page **pagelist;
unsigned long pages;
struct mm_struct *mm = current->mm;
unsigned long vdso_base = 0;
int retval = 0;
if (!vdso_ready)
return 0;
mm->context.vdso_base = 0;
pagelist = vdso_pagelist;
pages = vdso_pages;
#ifdef CONFIG_COMPAT
if (is_compat_task()) {
pagelist = vdso32_pagelist;
pages = vdso32_pages;
}
#endif
/*
* vDSO has a problem and was disabled, just don't "enable" it for the
* process.
*/
if (pages == 0)
return 0;
vdso_base = get_unmapped_area(NULL, vdso_base,
(pages << PAGE_SHIFT) +
((VDSO_ALIGNMENT - 1) & PAGE_MASK),
0, 0);
if (IS_ERR_VALUE(vdso_base)) {
retval = vdso_base;
return retval;
}
/* Add required alignment. */
vdso_base = ALIGN(vdso_base, VDSO_ALIGNMENT);
/*
* Put vDSO base into mm struct. We need to do this before calling
* install_special_mapping or the perf counter mmap tracking code
* will fail to recognise it as a vDSO (since arch_vma_name fails).
*/
mm->context.vdso_base = vdso_base;
/*
* our vma flags don't have VM_WRITE so by default, the process isn't
* allowed to write those pages.
* gdb can break that with ptrace interface, and thus trigger COW on
* those pages but it's then your responsibility to never do that on
* the "data" page of the vDSO or you'll stop getting kernel updates
* and your nice userland gettimeofday will be totally dead.
* It's fine to use that for setting breakpoints in the vDSO code
* pages though
*/
retval = install_special_mapping(mm, vdso_base,
pages << PAGE_SHIFT,
VM_READ|VM_EXEC |
VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC,
pagelist);
if (retval)
mm->context.vdso_base = 0;
return retval;
}
static __init int vdso_func(char *s)
{
return kstrtouint(s, 0, &vdso_enabled);
}
__setup("vdso=", vdso_func);

View File

@@ -0,0 +1,118 @@
# Symbols present in the vdso
vdso-syms = rt_sigreturn gettimeofday
# Files to link into the vdso
obj-vdso = $(patsubst %, v%.o, $(vdso-syms))
# Build rules
targets := $(obj-vdso) vdso.so vdso.so.dbg vdso.lds
obj-vdso := $(addprefix $(obj)/, $(obj-vdso))
# vdso32 is only for tilegx -m32 compat task.
VDSO32-$(CONFIG_COMPAT) := y
obj-y += vdso.o
obj-$(VDSO32-y) += vdso32.o
extra-y += vdso.lds
CPPFLAGS_vdso.lds += -P -C -U$(ARCH)
# vDSO code runs in userspace and -pg doesn't help with profiling anyway.
CFLAGS_REMOVE_vdso.o = -pg
CFLAGS_REMOVE_vdso32.o = -pg
CFLAGS_REMOVE_vrt_sigreturn.o = -pg
CFLAGS_REMOVE_vrt_sigreturn32.o = -pg
CFLAGS_REMOVE_vgettimeofday.o = -pg
CFLAGS_REMOVE_vgettimeofday32.o = -pg
ifdef CONFIG_FEEDBACK_COLLECT
# vDSO code runs in userspace, not collecting feedback data.
CFLAGS_REMOVE_vdso.o = -ffeedback-generate
CFLAGS_REMOVE_vdso32.o = -ffeedback-generate
CFLAGS_REMOVE_vrt_sigreturn.o = -ffeedback-generate
CFLAGS_REMOVE_vrt_sigreturn32.o = -ffeedback-generate
CFLAGS_REMOVE_vgettimeofday.o = -ffeedback-generate
CFLAGS_REMOVE_vgettimeofday32.o = -ffeedback-generate
endif
# Disable gcov profiling for VDSO code
GCOV_PROFILE := n
# Force dependency
$(obj)/vdso.o: $(obj)/vdso.so
# link rule for the .so file, .lds has to be first
SYSCFLAGS_vdso.so.dbg = $(c_flags)
$(obj)/vdso.so.dbg: $(src)/vdso.lds $(obj-vdso)
$(call if_changed,vdsold)
# We also create a special relocatable object that should mirror the symbol
# table and layout of the linked DSO. With ld -R we can then refer to
# these symbols in the kernel code rather than hand-coded addresses.
extra-y += vdso-syms.o
$(obj)/built-in.o: $(obj)/vdso-syms.o
$(obj)/built-in.o: ld_flags += -R $(obj)/vdso-syms.o
SYSCFLAGS_vdso.so.dbg = -shared -s -Wl,-soname=linux-vdso.so.1 \
$(call cc-ldoption, -Wl$(comma)--hash-style=sysv)
SYSCFLAGS_vdso_syms.o = -r
$(obj)/vdso-syms.o: $(src)/vdso.lds $(obj)/vrt_sigreturn.o FORCE
$(call if_changed,vdsold)
# strip rule for the .so file
$(obj)/%.so: OBJCOPYFLAGS := -S
$(obj)/%.so: $(obj)/%.so.dbg FORCE
$(call if_changed,objcopy)
# actual build commands
# The DSO images are built using a special linker script
# Add -lgcc so tilepro gets static muldi3 and lshrdi3 definitions.
# Make sure only to export the intended __vdso_xxx symbol offsets.
quiet_cmd_vdsold = VDSOLD $@
cmd_vdsold = $(CC) $(KCFLAGS) -nostdlib $(SYSCFLAGS_$(@F)) \
-Wl,-T,$(filter-out FORCE,$^) -o $@.tmp -lgcc && \
$(CROSS_COMPILE)objcopy \
$(patsubst %, -G __vdso_%, $(vdso-syms)) $@.tmp $@
# install commands for the unstripped file
quiet_cmd_vdso_install = INSTALL $@
cmd_vdso_install = cp $(obj)/$@.dbg $(MODLIB)/vdso/$@
vdso.so: $(obj)/vdso.so.dbg
@mkdir -p $(MODLIB)/vdso
$(call cmd,vdso_install)
vdso32.so: $(obj)/vdso32.so.dbg
$(call cmd,vdso_install)
vdso_install: vdso.so
vdso32_install: vdso32.so
KBUILD_AFLAGS_32 := $(filter-out -m64,$(KBUILD_AFLAGS))
KBUILD_AFLAGS_32 += -m32 -s
KBUILD_CFLAGS_32 := $(filter-out -m64,$(KBUILD_CFLAGS))
KBUILD_CFLAGS_32 += -m32 -fPIC -shared
obj-vdso32 = $(patsubst %, v%32.o, $(vdso-syms))
obj-vdso32 := $(addprefix $(obj)/, $(obj-vdso32))
targets += $(obj-vdso32) vdso32.so vdso32.so.dbg
$(obj-vdso32:%=%): KBUILD_AFLAGS = $(KBUILD_AFLAGS_32)
$(obj-vdso32:%=%): KBUILD_CFLAGS = $(KBUILD_CFLAGS_32)
$(obj)/vgettimeofday32.o: $(obj)/vgettimeofday.c
$(call if_changed,cc_o_c)
$(obj)/vrt_sigreturn32.o: $(obj)/vrt_sigreturn.S
$(call if_changed,as_o_S)
# Force dependency
$(obj)/vdso32.o: $(obj)/vdso32.so
SYSCFLAGS_vdso32.so.dbg = -m32 -shared -s -Wl,-soname=linux-vdso32.so.1 \
$(call cc-ldoption, -Wl$(comma)--hash-style=sysv)
$(obj)/vdso32.so.dbg: $(src)/vdso.lds $(obj-vdso32)
$(call if_changed,vdsold)

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/init.h>
#include <linux/linkage.h>
#include <asm/page.h>
__PAGE_ALIGNED_DATA
.global vdso_start, vdso_end
.align PAGE_SIZE
vdso_start:
.incbin "arch/tile/kernel/vdso/vdso.so"
.align PAGE_SIZE
vdso_end:
.previous

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2012 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#define VDSO_VERSION_STRING LINUX_2.6
OUTPUT_ARCH(tile)
/* The ELF entry point can be used to set the AT_SYSINFO value. */
ENTRY(__vdso_rt_sigreturn);
SECTIONS
{
. = SIZEOF_HEADERS;
.hash : { *(.hash) } :text
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.note : { *(.note.*) } :text :note
.dynamic : { *(.dynamic) } :text :dynamic
.eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr
.eh_frame : { KEEP (*(.eh_frame)) } :text
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
/*
* This linker script is used both with -r and with -shared.
* For the layouts to match, we need to skip more than enough
* space for the dynamic symbol table et al. If this amount
* is insufficient, ld -shared will barf. Just increase it here.
*/
. = 0x1000;
.text : { *(.text .text.*) } :text
.data : {
*(.got.plt) *(.got)
*(.data .data.* .gnu.linkonce.d.*)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
}
}
/*
* We must supply the ELF program headers explicitly to get just one
* PT_LOAD segment, and set the flags explicitly to make segments read-only.
*/
PHDRS
{
text PT_LOAD FLAGS(5) FILEHDR PHDRS; /* PF_R|PF_X */
dynamic PT_DYNAMIC FLAGS(4); /* PF_R */
note PT_NOTE FLAGS(4); /* PF_R */
eh_frame_hdr PT_GNU_EH_FRAME;
}
/*
* This controls what userland symbols we export from the vDSO.
*/
VERSION
{
VDSO_VERSION_STRING {
global:
__vdso_rt_sigreturn;
__vdso_gettimeofday;
gettimeofday;
local:*;
};
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2013 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/init.h>
#include <linux/linkage.h>
#include <asm/page.h>
__PAGE_ALIGNED_DATA
.global vdso32_start, vdso32_end
.align PAGE_SIZE
vdso32_start:
.incbin "arch/tile/kernel/vdso/vdso32.so"
.align PAGE_SIZE
vdso32_end:
.previous

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2012 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#define VDSO_BUILD /* avoid some shift warnings for -m32 in <asm/page.h> */
#include <linux/time.h>
#include <asm/timex.h>
#include <asm/vdso.h>
#if CHIP_HAS_SPLIT_CYCLE()
static inline cycles_t get_cycles_inline(void)
{
unsigned int high = __insn_mfspr(SPR_CYCLE_HIGH);
unsigned int low = __insn_mfspr(SPR_CYCLE_LOW);
unsigned int high2 = __insn_mfspr(SPR_CYCLE_HIGH);
while (unlikely(high != high2)) {
low = __insn_mfspr(SPR_CYCLE_LOW);
high = high2;
high2 = __insn_mfspr(SPR_CYCLE_HIGH);
}
return (((cycles_t)high) << 32) | low;
}
#define get_cycles get_cycles_inline
#endif
/*
* Find out the vDSO data page address in the process address space.
*/
inline unsigned long get_datapage(void)
{
unsigned long ret;
/* vdso data page located in the 2nd vDSO page. */
asm volatile ("lnk %0" : "=r"(ret));
ret &= ~(PAGE_SIZE - 1);
ret += PAGE_SIZE;
return ret;
}
int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz)
{
cycles_t cycles;
unsigned long count, sec, ns;
volatile struct vdso_data *vdso_data;
vdso_data = (struct vdso_data *)get_datapage();
/* The use of the timezone is obsolete, normally tz is NULL. */
if (unlikely(tz != NULL)) {
while (1) {
/* Spin until the update finish. */
count = vdso_data->tz_update_count;
if (count & 1)
continue;
tz->tz_minuteswest = vdso_data->tz_minuteswest;
tz->tz_dsttime = vdso_data->tz_dsttime;
/* Check whether updated, read again if so. */
if (count == vdso_data->tz_update_count)
break;
}
}
if (unlikely(tv == NULL))
return 0;
while (1) {
/* Spin until the update finish. */
count = vdso_data->tb_update_count;
if (count & 1)
continue;
cycles = (get_cycles() - vdso_data->xtime_tod_stamp);
ns = (cycles * vdso_data->mult) >> vdso_data->shift;
sec = vdso_data->xtime_clock_sec;
ns += vdso_data->xtime_clock_nsec;
if (ns >= NSEC_PER_SEC) {
ns -= NSEC_PER_SEC;
sec += 1;
}
/* Check whether updated, read again if so. */
if (count == vdso_data->tb_update_count)
break;
}
tv->tv_sec = sec;
tv->tv_usec = ns / 1000;
return 0;
}
int gettimeofday(struct timeval *tv, struct timezone *tz)
__attribute__((weak, alias("__vdso_gettimeofday")));

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2012 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*/
#include <linux/linkage.h>
#include <arch/abi.h>
#include <asm/unistd.h>
/*
* Note that libc has a copy of this function that it uses to compare
* against the PC when a stack backtrace ends, so if this code is
* changed, the libc implementation(s) should also be updated.
*/
ENTRY(__vdso_rt_sigreturn)
moveli TREG_SYSCALL_NR_NAME, __NR_rt_sigreturn
swint1
/* We don't use ENDPROC to avoid tagging this symbol as FUNC,
* which confuses the perf tool.
*/
END(__vdso_rt_sigreturn)

View File

@@ -0,0 +1,95 @@
#include <asm-generic/vmlinux.lds.h>
#include <asm/page.h>
#include <asm/cache.h>
#include <asm/thread_info.h>
#include <hv/hypervisor.h>
/* Text loads starting from the supervisor interrupt vector address. */
#define TEXT_OFFSET MEM_SV_INTRPT
OUTPUT_ARCH(tile)
ENTRY(_start)
jiffies = jiffies_64;
PHDRS
{
intrpt1 PT_LOAD ;
text PT_LOAD ;
data PT_LOAD ;
}
SECTIONS
{
/* Text is loaded with a different VA than data; start with text. */
#undef LOAD_OFFSET
#define LOAD_OFFSET TEXT_OFFSET
/* Interrupt vectors */
.intrpt1 (LOAD_OFFSET) : AT ( 0 ) /* put at the start of physical memory */
{
_text = .;
_stext = .;
*(.intrpt1)
} :intrpt1 =0
/* Hypervisor call vectors */
#include "hvglue.lds"
/* Now the real code */
. = ALIGN(0x20000);
.text : AT (ADDR(.text) - LOAD_OFFSET) {
HEAD_TEXT
SCHED_TEXT
LOCK_TEXT
__fix_text_end = .; /* tile-cpack won't rearrange before this */
TEXT_TEXT
*(.text.*)
*(.coldtext*)
*(.fixup)
*(.gnu.warning)
} :text =0
_etext = .;
/* "Init" is divided into two areas with very different virtual addresses. */
INIT_TEXT_SECTION(PAGE_SIZE)
/* Now we skip back to PAGE_OFFSET for the data. */
. = (. - TEXT_OFFSET + PAGE_OFFSET);
#undef LOAD_OFFSET
#define LOAD_OFFSET PAGE_OFFSET
. = ALIGN(PAGE_SIZE);
VMLINUX_SYMBOL(_sinitdata) = .;
INIT_DATA_SECTION(16) :data =0
PERCPU_SECTION(L2_CACHE_BYTES)
. = ALIGN(PAGE_SIZE);
VMLINUX_SYMBOL(_einitdata) = .;
_sdata = .; /* Start of data section */
RO_DATA_SECTION(PAGE_SIZE)
/* initially writeable, then read-only */
. = ALIGN(PAGE_SIZE);
__w1data_begin = .;
.w1data : AT(ADDR(.w1data) - LOAD_OFFSET) {
VMLINUX_SYMBOL(__w1data_begin) = .;
*(.w1data)
VMLINUX_SYMBOL(__w1data_end) = .;
}
RW_DATA_SECTION(L2_CACHE_BYTES, PAGE_SIZE, THREAD_SIZE)
_edata = .;
EXCEPTION_TABLE(L2_CACHE_BYTES)
NOTES
BSS_SECTION(8, PAGE_SIZE, 1)
_end = . ;
STABS_DEBUG
DWARF_DEBUG
DISCARDS
}