/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2004 Mips Technologies, Inc
* Copyright (C) 2008 Kevin D. Kissell
*/
#include <linux/clockchips.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/cpumask.h>
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
#include <linux/module.h>
#include <linux/ftrace.h>
#include <linux/slab.h>
#include <asm/cpu.h>
#include <asm/processor.h>
#include <linux/atomic.h>
#include <asm/hardirq.h>
#include <asm/hazards.h>
#include <asm/irq.h>
#include <asm/idle.h>
#include <asm/mmu_context.h>
#include <asm/mipsregs.h>
#include <asm/cacheflush.h>
#include <asm/time.h>
#include <asm/addrspace.h>
#include <asm/smtc.h>
#include <asm/smtc_proc.h>
#include <asm/setup.h>
/*
* SMTC Kernel needs to manipulate low-level CPU interrupt mask
* in do_IRQ. These are passed in setup_irq_smtc() and stored
* in this table.
*/
unsigned long irq_hwmask[NR_IRQS];
#define LOCK_MT_PRA() \
local_irq_save(flags); \
mtflags = dmt()
#define UNLOCK_MT_PRA() \
emt(mtflags); \
local_irq_restore(flags)
#define LOCK_CORE_PRA() \
local_irq_save(flags); \
mtflags = dvpe()
#define UNLOCK_CORE_PRA() \
evpe(mtflags); \
local_irq_restore(flags)
/*
* Data structures purely associated with SMTC parallelism
*/
/*
* Table for tracking ASIDs whose lifetime is prolonged.
*/
asiduse smtc_live_asid[MAX_SMTC_TLBS][MAX_SMTC_ASIDS];
/*
* Number of InterProcessor Interrupt (IPI) message buffers to allocate
*/
#define IPIBUF_PER_CPU 4
struct smtc_ipi_q IPIQ[NR_CPUS];
static struct smtc_ipi_q freeIPIq;
/*
* Number of FPU contexts for each VPE
*/
static int smtc_nconf1[MAX_SMTC_VPES];
/* Forward declarations */
void ipi_decode(struct smtc_ipi *);
static void post_direct_ipi(int cpu, struct smtc_ipi *pipi);
static void setup_cross_vpe_interrupts(unsigned int nvpe);
void init_smtc_stats(void);
/* Global SMTC Status */
unsigned int smtc_status;
/* Boot command line configuration overrides */
static int vpe0limit;
static int ipibuffers;
static int nostlb;
static int asidmask;
unsigned long smtc_asid_mask = 0xff;
static int __init vpe0tcs(char *str)
{
get_option(&str, &vpe0limit);
return 1;
}
static int __init ipibufs(char *str)
{
get_option(&str, &ipibuffers);
return 1;
}
static int __init stlb_disable(char *s)
{
nostlb = 1;
return 1;
}
static int __init asidmask_set(char *str)
{
get_option(&str, &asidmask);
switch (asidmask) {
case 0x1:
case 0x3:
case 0x7:
case 0xf:
case 0x1f:
case 0x3f:
case 0x7f:
case 0xff:
smtc_asid_mask = (unsigned long)asidmask;
break;
default:
printk("ILLEGAL ASID mask 0x%x from command line\n", asidmask);
}
return 1;
}
__setup("vpe0tcs=", vpe0tcs);
__setup("ipibufs=", ipibufs);
__setup("nostlb", stlb_disable);
__setup("asidmask=", asidmask_set);
#ifdef CONFIG_SMTC_IDLE_HOOK_DEBUG
static int hang_trig;
static int __init hangtrig_enable(char *s)
{
hang_trig = 1;
return 1;
}
__setup("hangtrig", hangtrig_enable);
#define DEFAULT_BLOCKED_IPI_LIMIT 32
static int timerq_limit = DEFAULT_BLOCKED_IPI_LIMIT;
static int __init tintq(char *str)
{
get_option(&str, &timerq_limit);
return 1;
}
__setup("tintq=", tintq);
static int imstuckcount[MAX_SMTC_VPES][8];
/* vpemask represents IM/IE bits of per-VPE Status registers, low-to-high */
static int vpemask[MAX_SMTC_VPES][8] = {
{0, 0, 1, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 1}
};
int tcnoprog[NR_CPUS];
static atomic_t idle_hook_initialized = ATOMIC_INIT(0);
static int clock_hang_reported[NR_CPUS];
#endif /* CONFIG_SMTC_IDLE_HOOK_DEBUG */
/*
* Configure shared TLB - VPC configuration bit must be set by caller
*/
static void smtc_configure_tlb(void)
{
int i, tlbsiz, vpes;
unsigned long mvpconf0;
unsigned long config1val;
/* Set up ASID preservation table */
for (vpes=0; vpes<MAX_SMTC_TLBS; vpes++) {
for(i = 0; i < MAX_SMTC_ASIDS; i++) {
smtc_live_asid[vpes][i] = 0;
}
}
mvpconf0 = read_c0_mvpconf0();
if ((vpes = ((mvpconf0 & MVPCONF0_PVPE)
>> MVPCONF0_PVPE_SHIFT) + 1) > 1) {
/* If we have multiple VPEs, try to share the TLB */
if ((mvpconf0 & MVPCONF0_TLBS) && !nostlb) {
/*
* If TLB sizing is programmable, shared TLB
* size is the total available complement.
* Otherwise, we have to take the sum of all
* static VPE TLB entries.
*/
if ((tlbsiz = ((mvpconf0 & MVPCONF0_PTLBE)
>> MVPCONF0_PTLBE_SHIFT)) == 0) {
/*
* If there's more than one VPE, there had better
* be more than one TC, because we need one to bind
* to each VPE in turn to be able to read
* its configuration state!
*/
settc(1);
/* Stop the TC from doing anything foolish */
write_tc_c0_tchalt(TCHALT_H);
mips_ihb();
/* No need to un-Halt - that happens later anyway */
for (i=0; i < vpes; i++) {
write_tc_c0_tcbind(i);
/*
* To be 100% sure we're really getting the right
* information, we exit the configuration state
* and do an IHB after each rebinding.
*/
write_c0_mvpcontrol(
read_c0_mvpcontrol() & ~ MVPCONTROL_VPC );
mips_ihb();
/*
* Only count if the MMU Type indicated is TLB
*/
if (((read_vpe_c0_config() & MIPS_CONF_MT) >> 7) == 1) {
config1val = read_vpe_c0_config1();
tlbsiz += ((config1val >> 25) & 0x3f) + 1;
}
/* Put core back in configuration state */
write_c0_mvpcontrol(
read_c0_mvpcontrol() | MVPCONTROL_VPC );
mips_ihb();
}
}
write_c0_mvpcontrol(read_c0_mvpcontrol() | MVPCONTROL_STLB);
ehb();
/*
* Setup kernel data structures to use software total,
* rather than read the per-VPE Config1 value. The values
* for "CPU 0" gets copied to all the other CPUs as part
* of their initialization in smtc_cpu_setup().
*/
/* MIPS32 limits TLB indices to 64 */
if (tlbsiz > 64)
tlbsiz = 64;
cpu_data[0].tlbsize = current_cpu_data.tlbsize = tlbsiz;
smtc_status |= SMTC_TLB_SHARED;
local_flush_tlb_all();
printk("TLB of %d entry pairs shared by %d VPEs\n",
tlbsiz, vpes);
} else {
printk("WARNING: TLB Not Sharable on SMTC Boot!\n");
}
}
}
/*
* Incrementally build the CPU map out of constituent MIPS MT cores,
* using the specified available VPEs and TCs. Plaform code needs
* to ensure that each MIPS MT core invokes this routine on reset,
* one at a time(!).
*
* This version of the build_cpu_map and prepare_cpus routines assumes
* that *all* TCs of a MIPS MT core will be used for Linux, and that
* they will be spread across *all* available VPEs (to minimise the
* loss of efficiency due to exception service serialization).
* An improved version would pi
|